---
# System prepended metadata

title: '# SDK v2'

---

Правки SDK:
- [x] Мигрировать transaction_id на UUID вместо int
- [x] Поддерживать новый формат сообщения MulticastDownlinkResult
- [x] Использовать 1 эндпоинт для streaming вместо 2х upstream/downstream (Только касательно структуры EndpointSchema)
- [x] Обновить (де)сериализацию из/в контракты go-ran-routing
- [ ] Реализовать модуляции, которые раньше не поддерживались
    - [x] FSK upstream
    - [x] FSK downstream
    - [ ] FHSS downstream (***не поддерживается goranrouting***)
- [ ] Новый подход для работы с единой точкой подключения к streaming (\*см. ниже)
    - [x] (Де)сериализация всех типов сообщений, передаваемых через WS
        - [x] Разделить (де)сериализаторы DownstreamAck/DownstreamResult/MulticastDownstreamResult
    - [x] Реализовать low-level sdk для стриминга через 1 WS
    - [x] Новый низкоуровневый streaming connection без асинхронного воркера 
    - [ ] Слой обратной совместимости для новго streaming-API (обсудить, нужен ли?) (***не нужен***)
- [x] Обновить unit-тесты
    - [x] upstream
    - [x] downstream
    - [x] downstream multicast

Правки бриджей:
- [x] ChirpStack v4
    - [x] Мигрировать transaction_id на UUID вместо int
    - [x] Обрабатывать новый MulticastDownlinkResult
    - [x] Новый Streaming API
        - [x] Использовать 1 connection - объект, согласно новому API
        - [x] Уметь фильтровать трафик Upstream/Downstream из одного подключения
        - [x] Поддержка и восстановление соединений при закрытии\падении (на основании существущего кода)
    - [x] Мелкие изменения в протоколе ran-routing (переименованные поля, etc..)
    - [x] Обновить интеграционные тесты
- [ ] ChirpStack v3
    - [ ] *(скопировать сюда все пункты по необходимости)*
- [ ] TTN
    - [ ] *(скопировать сюда все пункты по необходимости)*

Прочие вопросы:
- [ ] Нужно ли отказаться от DownstreamAck?
- [ ] Работа с новыми функциями workspace:
    - [x] Wildcards. Можно ли будет их менять через SDK? (**Нет**)
    - [ ] Мигрировать client_id на UUID вместо int?
- [ ] *offtop*: А будет ли у нас SDK для UI? Создавать клиентов, управлять токенами, настраивать trust/wildcard etc.


---


## Идеи по работе с новым single-connection ws

**Вариант 1** "Как есть": Оставить разделение на upstream/downstream, убрать возможность управлять соединениями, переложив управление на более "умный" ConnectionManager.

За:
- Такая реализация уже есть
- Никаких дополнительных изменений в бриджах

Против:
- Сложная внутренняя структура, можно наделать ошибок с проксями
- Нельзя рулить настоящим соединением напрямую, больше подходит для высокоуровневого SDK

```python
async with Core() as core:
    async with core.downstream() as downstream, core.upstream() as upstream:
        async for msg in upstream.stream():
            ...
            await upstream.send_upstream_ack(msg.transaction_id, dev_eui=msg.dev_euis[0], mic=correct_mic)
            await downstream.send_downstream_obj(...)
            await downstream.recv(timeout=1)
```

---

**Вариант 2** "Лапша": Объединить все методы по работе со stream-трафиком в единый объект "Stream"

За:
- Плоская логика, описывает все действия, доступные со streaming-api
- Саше понравится

Против:
- Больше изменений в коде бриджей, поскольку нужно убрать разделение на upstream/dowsntream
- Не совсем ясно, разделять ли получаемый трафик, или делать реализацию в вызывающем коде? Если разделять - то как лучше сделать?

```python
class Stream:
    async def connect(self): ...
    def close(self): ...
    async def recv_upstream(self): ...
    async def recv_downstream(self): ...
    async def listen_upstream(self): ...
    async def listen_downstream(self): ...
    async def send_upstream_ack(self, **kwargs): ...
    async def send_downstream_obj(self, **kwargs): ...


async with Core() as core:
    async with core.stream() as stream:
        async for msg in stream.listen_upstream():
            ...
            await stream.send_upstream_ack(msg.transaction_id, dev_eui=msg.dev_euis[0], mic=correct_mic)
            await stream.send_downstream_obj(...)
            await stream.recv_downstream(timeout=1)
```

---

**Вариант 3** "Вроде норм": Логически разделять работу со stream-трафиком на upstream/dowsntream при помощи proxy, управлять общим для них соединением.

За:
- Меньше изменений в бриджах, поскольку там разные части кода работают с upstream и dowsntream. Вместо реальных подключений просто передаём proxy-объект.
- Похоже на подходящие для этой задачи абстракции.

Против:
- ??

```python
class UpstreamProxy:
    async def recv(self): ...
    async def listen(self): ...
    async def send_upstream_ack(self, **kwargs): ...

class DownstreamProxy:
    async def recv(self): ...
    async def listen(self): ...
    async def send_downstream_obj(self, **kwargs): ...

class Stream:
    def __init__(self) -> None:
        self.upstream = UpstreamProxy(self)
        self.downstream = DownstreamProxy(self)

    async def connect(self): ...
    def close(self): ...


async with Core() as core:
    async with core.stream() as stream:
        async for msg in stream.upstream.listen():
            ...
            await stream.upstream.send_upstream_ack(msg.transaction_id, dev_eui=msg.dev_euis[0], mic=correct_mic)
            await stream.downstream.send_downstream_obj(...)
            await stream.downstream.recv(timeout=1)
```

---

**Вариант 2/3** "Лапша + Proxy": Как в варианте **1.**, но есть внешний Proxy, который не является core-частью Streaming api.

За:
- Есть преимущества 1 и 2 вариантов

Против:
- Есть недостатки 1 и 2 вариантов

```python

# ran.routing.core.streaming
class Stream:
    async def connect(self): ...
    def close(self): ...
    async def recv_upstream(self): ...
    async def recv_downstream(self): ...
    async def listen_upstream(self): ...
    async def listen_downstream(self): ...
    async def send_upstream_ack(self, **kwargs): ...
    async def send_downstream_obj(self, **kwargs): ...

# ran.routing.helpers.streaming
class UpstreamProxy:
    def __init__(self, stream: Stream):
        self.stream = stream
    async def recv(self): return await self.stream.recv_upstream()
    async def listen(self): ...
    async def send_upstream_ack(self, **kwargs): ...

class DownstreamProxy:
    def __init__(self, stream: Stream):
        self.stream = stream
    async def recv(self): return await self.stream.recv_downstream()
    async def listen(self): ...
    async def send_downstream_obj(self, **kwargs): ...

def split_stream(stream: Stream) -> (UpstreamProxy, DownstreamProxy)
    return UpstreamProxy(stream), DownstreamProxy(stream)

async with Core() as core:
    async with core.stream() as stream:
        upstream, downstream = split_stream(stream)
        async for msg in upstream.listen():
            ...
            await upstream.send_upstream_ack(msg.transaction_id, dev_eui=msg.dev_euis[0], mic=correct_mic)
            await downstream.send_downstream_obj(...)
            await downstream.recv(timeout=1)
```

---

**Вариант 4** новая реализация низкого уровня

```python

# ran.routing.core.streaming
class Stream:
    async def connect(self): ...
    def close(self): ...
    async def recv(self) -> FromRan: ...
    async def send(self, data: bytes) -> FromLns: ...

```

