Initial dependency build
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
def hello() -> str:
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
return "Hello from dynalab-protocol!"
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|||||||
71
src/dynalab_protocol/models.py
Normal file
71
src/dynalab_protocol/models.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from typing import Annotated, Literal
|
||||||
|
from uuid import UUID
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
PROTOCOL_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
|
class VersionDescriptor(BaseModel):
|
||||||
|
type: Literal["alpha", "beta", "release"]
|
||||||
|
major: int
|
||||||
|
minor: int
|
||||||
|
patch: int
|
||||||
|
|
||||||
|
|
||||||
|
class SignalDescriptor(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
name: str
|
||||||
|
type: Literal["number", "binary"]
|
||||||
|
min_value: float | None = None
|
||||||
|
max_value: float | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class DynaLabHello(BaseModel):
|
||||||
|
type: Literal["dynalab_hello"] = "dynalab_hello"
|
||||||
|
instance_id: UUID
|
||||||
|
version: VersionDescriptor
|
||||||
|
protocol_version: int = PROTOCOL_VERSION
|
||||||
|
|
||||||
|
heartbeat_interval_ms: int = 1000
|
||||||
|
heartbeat_timeout_ms: int = 5000
|
||||||
|
|
||||||
|
protocol_version: int = PROTOCOL_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectorHello(BaseModel):
|
||||||
|
type: Literal["connector_hello"] = "connector_hello"
|
||||||
|
connector_uuid: UUID
|
||||||
|
protocol_version: int = PROTOCOL_VERSION
|
||||||
|
|
||||||
|
connector_name: str
|
||||||
|
connector_version: str
|
||||||
|
|
||||||
|
signals: list[SignalDescriptor]
|
||||||
|
|
||||||
|
|
||||||
|
class HandshakeAccepted(BaseModel):
|
||||||
|
type: Literal["handshake_accepted"] = "handshake_accepted"
|
||||||
|
|
||||||
|
accepted_signals: list[UUID]
|
||||||
|
|
||||||
|
|
||||||
|
class HandshakeRejected(BaseModel):
|
||||||
|
type: Literal["handshake_rejected"] = "handshake_rejected"
|
||||||
|
reason: str
|
||||||
|
|
||||||
|
|
||||||
|
class Heartbeat(BaseModel):
|
||||||
|
type: Literal["heartbeat"] = "heartbeat"
|
||||||
|
sequence: int
|
||||||
|
send_timestamp: int
|
||||||
|
return_timestamp: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
ProtocolMessage = Annotated[
|
||||||
|
DynaLabHello | ConnectorHello | HandshakeAccepted | HandshakeRejected | Heartbeat,
|
||||||
|
Field(discriminator="type"),
|
||||||
|
]
|
||||||
33
src/dynalab_protocol/wire.py
Normal file
33
src/dynalab_protocol/wire.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from asyncio import StreamReader, StreamWriter
|
||||||
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
|
from dynalab_protocol.models import ProtocolMessage
|
||||||
|
|
||||||
|
|
||||||
|
_MESSAGE_ADAPTER = TypeAdapter(ProtocolMessage)
|
||||||
|
|
||||||
|
|
||||||
|
async def write_message(writer: StreamWriter, message: ProtocolMessage) -> None:
|
||||||
|
writer.write(message.model_dump_json().encode("utf-8") + b"\n")
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
|
||||||
|
async def read_message(
|
||||||
|
reader: StreamReader, *, max_frame_bytes: int = 65_536
|
||||||
|
) -> ProtocolMessage:
|
||||||
|
line = await reader.readline()
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
raise ConnectionError("Peer disconnected")
|
||||||
|
|
||||||
|
if len(line) > max_frame_bytes:
|
||||||
|
raise ValueError("Incoming protocol frame exceeds maximum size")
|
||||||
|
|
||||||
|
if not line.endswith(b"\n"):
|
||||||
|
raise ValueError("Protocol frame is missing newline delimiter")
|
||||||
|
|
||||||
|
return _MESSAGE_ADAPTER.validate_json(line)
|
||||||
Reference in New Issue
Block a user