Skip to content
This repository was archived by the owner on May 3, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions TASK.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

## 2025-08-11
- [x] Fix flake8 errors across the codebase.
- [x] Add schema validation and auth token handling to EmergencyManagement example.
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ components:
allOf:
- type: object
properties:
auth_token:
type: string
description: Authentication token
callsign:
type: string
description: >-
Expand Down Expand Up @@ -187,11 +190,15 @@ components:
Green – No immediate threats and currently in a secure area
$ref: '#/components/schemas/EAMStatus'
required:
- auth_token
- callsign
Event:
allOf:
- type: object
properties:
auth_token:
type: string
description: Authentication token
uid:
type: integer
description: >-
Expand Down Expand Up @@ -231,6 +238,7 @@ components:
type: string
x-reference: '#/components/schemas/Point'
required:
- auth_token
- uid
Point:
allOf:
Expand Down
3 changes: 3 additions & 0 deletions examples/EmergencyManagement/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ python Server/server_emergency.py
python client/client_emergency.py
```

Both server and client use a hardcoded authentication token. Requests will only
be processed when the tokens match.

The client first sends a `CreateEmergencyActionMessage` request and prints the
response returned by the server. It then issues a `RetrieveEmergencyActionMessage`
command for the same callsign and displays the stored record, demonstrating that
Expand Down
45 changes: 29 additions & 16 deletions examples/EmergencyManagement/Server/controllers_emergency.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import asdict

from reticulum_openapi.controller import Controller, handle_exceptions
from examples.EmergencyManagement.Server.database import async_session
from examples.EmergencyManagement.Server.models_emergency import (
Expand All @@ -9,35 +10,42 @@

class EmergencyController(Controller):
@handle_exceptions
async def CreateEmergencyActionMessage(self, req: EmergencyActionMessage):
async def CreateEmergencyActionMessage(self, req: dict):
self.logger.info(f"CreateEAM: {req}")
data = {k: v for k, v in req.items() if k != "auth_token"}
eam = EmergencyActionMessage(**data)
async with async_session() as session:
await EmergencyActionMessage.create(session, **asdict(req))
return req
await EmergencyActionMessage.create(session, **asdict(eam))
return eam

@handle_exceptions
async def DeleteEmergencyActionMessage(self, callsign: str):
async def DeleteEmergencyActionMessage(self, req: dict):
callsign = req.get("callsign")
self.logger.info(f"DeleteEAM callsign={callsign}")
async with async_session() as session:
deleted = await EmergencyActionMessage.delete(session, callsign)
return {"status": "deleted" if deleted else "not_found", "callsign": callsign}

@handle_exceptions
async def ListEmergencyActionMessage(self):
async def ListEmergencyActionMessage(self, _payload: dict | None = None):
self.logger.info("ListEAM")
async with async_session() as session:
items = await EmergencyActionMessage.list(session)
return items

@handle_exceptions
async def PutEmergencyActionMessage(self, req: EmergencyActionMessage):
async def PutEmergencyActionMessage(self, req: dict):
self.logger.info(f"PutEAM: {req}")
data = {k: v for k, v in req.items() if k != "auth_token"}
async with async_session() as session:
updated = await EmergencyActionMessage.update(session, req.callsign, **asdict(req))
updated = await EmergencyActionMessage.update(
session, data["callsign"], **data
)
return updated

@handle_exceptions
async def RetrieveEmergencyActionMessage(self, callsign: str):
async def RetrieveEmergencyActionMessage(self, req: dict):
callsign = req.get("callsign")
self.logger.info(f"RetrieveEAM callsign={callsign}")
async with async_session() as session:
item = await EmergencyActionMessage.get(session, callsign)
Expand All @@ -46,35 +54,40 @@ async def RetrieveEmergencyActionMessage(self, callsign: str):

class EventController(Controller):
@handle_exceptions
async def CreateEvent(self, req: Event):
async def CreateEvent(self, req: dict):
self.logger.info(f"CreateEvent: {req}")
data = {k: v for k, v in req.items() if k != "auth_token"}
ev = Event(**data)
async with async_session() as session:
await Event.create(session, **asdict(req))
return req
await Event.create(session, **asdict(ev))
return ev

@handle_exceptions
async def DeleteEvent(self, uid: str):
async def DeleteEvent(self, req: dict):
uid = req.get("uid")
self.logger.info(f"DeleteEvent uid={uid}")
async with async_session() as session:
deleted = await Event.delete(session, int(uid))
return {"status": "deleted" if deleted else "not_found", "uid": uid}

@handle_exceptions
async def ListEvent(self):
async def ListEvent(self, _payload: dict | None = None):
self.logger.info("ListEvent")
async with async_session() as session:
events = await Event.list(session)
return events

@handle_exceptions
async def PutEvent(self, req: Event):
async def PutEvent(self, req: dict):
self.logger.info(f"PutEvent: {req}")
data = {k: v for k, v in req.items() if k != "auth_token"}
async with async_session() as session:
updated = await Event.update(session, req.uid, **asdict(req))
updated = await Event.update(session, data["uid"], **data)
return updated

@handle_exceptions
async def RetrieveEvent(self, uid: str):
async def RetrieveEvent(self, req: dict):
uid = req.get("uid")
self.logger.info(f"RetrieveEvent uid={uid}")
async with async_session() as session:
event = await Event.get(session, int(uid))
Expand Down
62 changes: 62 additions & 0 deletions examples/EmergencyManagement/Server/schemas_emergency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""JSON Schemas for the EmergencyManagement example."""

EMERGENCY_ACTION_MESSAGE_SCHEMA = {
"type": "object",
"properties": {
"auth_token": {"type": "string"},
"callsign": {"type": "string"},
"groupName": {"type": ["string", "null"]},
"securityStatus": {"type": ["string", "null"]},
"securityCapability": {"type": ["string", "null"]},
"preparednessStatus": {"type": ["string", "null"]},
"medicalStatus": {"type": ["string", "null"]},
"mobilityStatus": {"type": ["string", "null"]},
"commsStatus": {"type": ["string", "null"]},
"commsMethod": {"type": ["string", "null"]},
},
"required": ["auth_token", "callsign"],
}

EVENT_SCHEMA = {
"type": "object",
"properties": {
"auth_token": {"type": "string"},
"uid": {"type": "integer"},
"how": {"type": ["string", "null"]},
"version": {"type": ["integer", "null"]},
"time": {"type": ["integer", "null"]},
"type": {"type": ["string", "null"]},
"stale": {"type": ["string", "null"]},
"start": {"type": ["string", "null"]},
"access": {"type": ["string", "null"]},
"opex": {"type": ["integer", "null"]},
"qos": {"type": ["integer", "null"]},
"detail": {"type": ["object", "null"]},
"point": {"type": ["object", "null"]},
},
"required": ["auth_token", "uid"],
}

AUTH_SCHEMA = {
"type": "object",
"properties": {"auth_token": {"type": "string"}},
"required": ["auth_token"],
}

CALLSIGN_SCHEMA = {
"type": "object",
"properties": {
"auth_token": {"type": "string"},
"callsign": {"type": "string"},
},
"required": ["auth_token", "callsign"],
}

UID_SCHEMA = {
"type": "object",
"properties": {
"auth_token": {"type": "string"},
"uid": {"type": "integer"},
},
"required": ["auth_token", "uid"],
}
4 changes: 3 additions & 1 deletion examples/EmergencyManagement/Server/server_emergency.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from examples.EmergencyManagement.Server.service_emergency import EmergencyService
from examples.EmergencyManagement.Server.database import init_db

AUTH_TOKEN = "secret-token"


async def main():
await init_db()
async with EmergencyService() as svc:
async with EmergencyService(auth_token=AUTH_TOKEN) as svc:
svc.announce()
await asyncio.sleep(30) # Run for 30 seconds then stop

Expand Down
65 changes: 50 additions & 15 deletions examples/EmergencyManagement/Server/service_emergency.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,72 @@
EmergencyController,
EventController,
)
from examples.EmergencyManagement.Server.models_emergency import (
EmergencyActionMessage,
Event,
from examples.EmergencyManagement.Server.schemas_emergency import (
AUTH_SCHEMA,
CALLSIGN_SCHEMA,
EMERGENCY_ACTION_MESSAGE_SCHEMA,
EVENT_SCHEMA,
UID_SCHEMA,
)


class EmergencyService(LXMFService):
"""Service with routes for the emergency management example."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, *args, auth_token: str | None = None, **kwargs):
super().__init__(*args, auth_token=auth_token, **kwargs)

eamc = EmergencyController()
evc = EventController()

self.add_route(
"CreateEmergencyActionMessage",
eamc.CreateEmergencyActionMessage,
EmergencyActionMessage,
payload_schema=EMERGENCY_ACTION_MESSAGE_SCHEMA,
)
self.add_route(
"DeleteEmergencyActionMessage",
eamc.DeleteEmergencyActionMessage,
payload_schema=CALLSIGN_SCHEMA,
)
self.add_route(
"ListEmergencyActionMessage",
eamc.ListEmergencyActionMessage,
payload_schema=AUTH_SCHEMA,
)
self.add_route("DeleteEmergencyActionMessage", eamc.DeleteEmergencyActionMessage)
self.add_route("ListEmergencyActionMessage", eamc.ListEmergencyActionMessage)
self.add_route(
"PutEmergencyActionMessage",
eamc.PutEmergencyActionMessage,
EmergencyActionMessage,
payload_schema=EMERGENCY_ACTION_MESSAGE_SCHEMA,
)
self.add_route(
"RetrieveEmergencyActionMessage",
eamc.RetrieveEmergencyActionMessage,
payload_schema=CALLSIGN_SCHEMA,
)
self.add_route("RetrieveEmergencyActionMessage", eamc.RetrieveEmergencyActionMessage)

self.add_route("CreateEvent", evc.CreateEvent, Event)
self.add_route("DeleteEvent", evc.DeleteEvent)
self.add_route("ListEvent", evc.ListEvent)
self.add_route("PutEvent", evc.PutEvent, Event)
self.add_route("RetrieveEvent", evc.RetrieveEvent)
self.add_route(
"CreateEvent",
evc.CreateEvent,
payload_schema=EVENT_SCHEMA,
)
self.add_route(
"DeleteEvent",
evc.DeleteEvent,
payload_schema=UID_SCHEMA,
)
self.add_route(
"ListEvent",
evc.ListEvent,
payload_schema=AUTH_SCHEMA,
)
self.add_route(
"PutEvent",
evc.PutEvent,
payload_schema=EVENT_SCHEMA,
)
self.add_route(
"RetrieveEvent",
evc.RetrieveEvent,
payload_schema=UID_SCHEMA,
)
22 changes: 14 additions & 8 deletions examples/EmergencyManagement/client/client_emergency.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,36 @@
EAMStatus,
)

AUTH_TOKEN = "secret-token"


async def main():
client = LXMFClient()
client = LXMFClient(auth_token=AUTH_TOKEN)
server_id = input("Server Identity Hash: ")
eam = EmergencyActionMessage(
callsign="Bravo1", groupName="Bravo",
securityStatus=EAMStatus.Green, securityCapability=EAMStatus.Green,
preparednessStatus=EAMStatus.Green, medicalStatus=EAMStatus.Green,
mobilityStatus=EAMStatus.Green, commsStatus=EAMStatus.Green,
commsMethod="VOIP"
callsign="Bravo1",
groupName="Bravo",
securityStatus=EAMStatus.Green,
securityCapability=EAMStatus.Green,
preparednessStatus=EAMStatus.Green,
medicalStatus=EAMStatus.Green,
mobilityStatus=EAMStatus.Green,
commsStatus=EAMStatus.Green,
commsMethod="VOIP",
)
resp = await client.send_command(
server_id, "CreateEmergencyActionMessage", eam, await_response=True
)
print("Create response:", resp)

# Retrieve the message back from the server to demonstrate persistence
retrieved = await client.send_command(
server_id,
"RetrieveEmergencyActionMessage",
eam.callsign,
{"callsign": eam.callsign},
await_response=True,
)
print("Retrieve response:", retrieved)


if __name__ == "__main__":
asyncio.run(main())
Loading
Loading