Cleanup, sensible exceptions
This commit is contained in:
parent
641649d964
commit
85a5f400bc
44
command.py
44
command.py
@ -1,5 +1,14 @@
|
|||||||
|
import json
|
||||||
import jsonschema
|
import jsonschema
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, asdict
|
||||||
|
|
||||||
|
|
||||||
|
class CommandArgumentError(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutionError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
@ -9,19 +18,25 @@ class Command:
|
|||||||
self.handler = handler
|
self.handler = handler
|
||||||
self.additional_properties = kwargs
|
self.additional_properties = kwargs
|
||||||
|
|
||||||
self._validator = jsonschema.validators.validator_for(
|
def __call__(self, handler_instance, payload):
|
||||||
schema, default=jsonschema.validators.Draft7Validator
|
|
||||||
)(schema=schema)
|
|
||||||
|
|
||||||
def validate(self, o) -> bool:
|
|
||||||
return self._validator.is_valid(o)
|
|
||||||
|
|
||||||
def __call__(self, handler_instance, args):
|
|
||||||
if not self.validate(args):
|
|
||||||
raise ValueError(f"Invalid arguments for command '{self.name}'")
|
|
||||||
if self.handler is None:
|
if self.handler is None:
|
||||||
raise RuntimeError(f"No handler bound for command '{self.name}'")
|
raise NotImplementedError(f"No handler bound for command '{self.name}'")
|
||||||
return self.handler(handler_instance, args)
|
|
||||||
|
try:
|
||||||
|
arguments = json.loads(payload)
|
||||||
|
jsonschema.validate(arguments, self.schema)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
raise CommandArgumentError(
|
||||||
|
f"Invalid JSON at line {e.lineno} column {e.colno}: {e.msg}"
|
||||||
|
)
|
||||||
|
except jsonschema.ValidationError as e:
|
||||||
|
raise CommandArgumentError(f"Schema error in {e.json_path}: {e.message}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.handler(handler_instance, arguments)
|
||||||
|
except Exception as e:
|
||||||
|
print("Internal command error: ", e)
|
||||||
|
raise CommandExecutionError(f"Command execution failed internally.")
|
||||||
|
|
||||||
|
|
||||||
def command(schema, **kwargs):
|
def command(schema, **kwargs):
|
||||||
@ -36,3 +51,6 @@ class CommandResponse:
|
|||||||
success: bool
|
success: bool
|
||||||
message: str = None
|
message: str = None
|
||||||
correlation: str = None
|
correlation: str = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return json.dumps(asdict(self))
|
||||||
|
|||||||
34
handler.py
34
handler.py
@ -1,10 +1,14 @@
|
|||||||
import asyncio
|
|
||||||
import aiomqtt
|
import aiomqtt
|
||||||
import json
|
import asyncio
|
||||||
from command import Command, CommandResponse
|
|
||||||
import inspect
|
import inspect
|
||||||
from paho.mqtt.properties import Properties
|
import paho
|
||||||
from dataclasses import asdict
|
|
||||||
|
from command import (
|
||||||
|
Command,
|
||||||
|
CommandResponse,
|
||||||
|
CommandArgumentError,
|
||||||
|
CommandExecutionError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MQTTHandler:
|
class MQTTHandler:
|
||||||
@ -47,9 +51,11 @@ class MQTTHandler:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def execute_command(
|
async def execute_command(
|
||||||
self, command_name: str, payload: str, properties: Properties = None
|
self,
|
||||||
|
command_name: str,
|
||||||
|
payload: str,
|
||||||
|
properties: paho.mqtt.properties.Properties = None,
|
||||||
):
|
):
|
||||||
|
|
||||||
async def respond(success: bool, message: str = None):
|
async def respond(success: bool, message: str = None):
|
||||||
if (
|
if (
|
||||||
properties is not None
|
properties is not None
|
||||||
@ -63,23 +69,17 @@ class MQTTHandler:
|
|||||||
)
|
)
|
||||||
await self.mqtt_client.publish(
|
await self.mqtt_client.publish(
|
||||||
properties.ResponseTopic,
|
properties.ResponseTopic,
|
||||||
json.dumps(
|
str(CommandResponse(success, str(message), correlation)),
|
||||||
asdict(CommandResponse(success, str(message), correlation))
|
|
||||||
),
|
|
||||||
qos=1,
|
qos=1,
|
||||||
retain=False,
|
retain=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
command = self.get_available_commands()[command_name]
|
command = self.get_available_commands()[command_name]
|
||||||
argument = json.loads(payload)
|
result = await command(self, payload)
|
||||||
result = await command(self, argument)
|
|
||||||
await respond(True, result)
|
await respond(True, result)
|
||||||
|
except (CommandArgumentError, CommandExecutionError) as e:
|
||||||
except json.decoder.JSONDecodeError as e:
|
await respond(False, f"{e}")
|
||||||
await respond(False, f"Failed to parse payload as JSON: {e}")
|
|
||||||
except ValueError as e:
|
|
||||||
await respond(False, f"Command payload does not match expected schema: {e}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to execute command {command_name} with unknown cause: ", e)
|
print(f"Failed to execute command {command_name} with unknown cause: ", e)
|
||||||
await respond(False, "Unexpected error")
|
await respond(False, "Unexpected error")
|
||||||
|
|||||||
5
main.py
5
main.py
@ -5,6 +5,7 @@ import aioserial
|
|||||||
import asyncio
|
import asyncio
|
||||||
import paho
|
import paho
|
||||||
import uuid
|
import uuid
|
||||||
|
import socket
|
||||||
|
|
||||||
from ubxhandler import UBXHandler
|
from ubxhandler import UBXHandler
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ BAUD = 115200
|
|||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
handler_id = "example-gps"
|
handler_id = f"example-gps-{socket.gethostname()}"
|
||||||
mqtt_host = "127.0.0.1"
|
mqtt_host = "127.0.0.1"
|
||||||
mqtt_port = 1883
|
mqtt_port = 1883
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ async def main():
|
|||||||
timeout=0.05, # 50 ms
|
timeout=0.05, # 50 ms
|
||||||
)
|
)
|
||||||
|
|
||||||
handler = UBXHandler(mqtt_client, "example-gps", serial_port)
|
handler = UBXHandler(mqtt_client, handler_id, serial_port)
|
||||||
await handler.run()
|
await handler.run()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,7 @@ class UBXHandler(MQTTHandler):
|
|||||||
if name.startswith("_"):
|
if name.startswith("_"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
topic = f"{self.topic_base}/{message.identity}/{name}"
|
topic = f"{self.property_topic}/{message.identity}/{name}"
|
||||||
await self.mqtt_client.publish(topic, value, qos=1, retain=True)
|
await self.mqtt_client.publish(topic, value, qos=1, retain=True)
|
||||||
else:
|
else:
|
||||||
# print("Unexpected response:", message)
|
# print("Unexpected response:", message)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user