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