Правки 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: ...
```