From 8edb09d13df60c6f04ffd7c10c730c15d28a132d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= <donal.murray@cern.ch> Date: Mon, 13 Apr 2020 19:24:05 +0100 Subject: [PATCH] Add command sending capabilities. Streamline commandFormat --- raspberry-dataserver/commsConstants.py | 37 +++++++++--- raspberry-dataserver/commsDebug.py | 8 +-- raspberry-dataserver/hevclient.py | 64 ++++++++------------ raspberry-dataserver/hevserver.py | 82 +++++++++++++------------- 4 files changed, 98 insertions(+), 93 deletions(-) diff --git a/raspberry-dataserver/commsConstants.py b/raspberry-dataserver/commsConstants.py index a5668a58..cb4ca007 100644 --- a/raspberry-dataserver/commsConstants.py +++ b/raspberry-dataserver/commsConstants.py @@ -1,5 +1,5 @@ from struct import Struct -from enum import Enum, auto +from enum import Enum, auto, unique import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') @@ -107,8 +107,6 @@ class dataFormat(BaseFormat): # for receiving dataFormat from microcontroller # fill the struct from a byteArray, def fromByteArray(self, byteArray): - logging.debug(f"Byte array of size {len(byteArray)}") - logging.debug(f"Byte array of size {byteArray}") self._byteArray = byteArray (self._version, self._fsm_state, @@ -182,16 +180,37 @@ class dataFormat(BaseFormat): # cmd type payload # ======================================= class commandFormat(BaseFormat): - def __init__(self): + def __init__(self, cmdCode=0, param=0): super().__init__() self._dataStruct = Struct("<BBI") self._byteArray = None self._type = payloadType.payloadCmd self._version = 0 - self._cmdCode = 0 - self._param = 0 + self._cmdCode = cmdCode + self._param = param + self.toByteArray() + + # manage direct reading and writing of member variables + @property + def cmdCode(self): + return self._cmdCode + + @cmdCode.setter + def cmdCode(self, cmdCodeIn): + self._cmdCode = cmdCodeIn + self.toByteArray() + + @property + def param(self): + return self._param + + @param.setter + def param(self, paramIn): + self._param = paramIn + self.toByteArray() + # print nicely def __repr__(self): return f"""{{ "version" : {self._version}, @@ -221,9 +240,10 @@ class commandFormat(BaseFormat): } return data +@unique class command_codes(Enum): - CMD_START = 1 - CMD_STOP = 2 + CMD_START = 0x1 + CMD_STOP = 0x2 # ======================================= # alarm type payload @@ -267,6 +287,7 @@ class alarmFormat(BaseFormat): } return data +@unique class alarm_codes(Enum): ALARM_START = 1 ALARM_STOP = 2 \ No newline at end of file diff --git a/raspberry-dataserver/commsDebug.py b/raspberry-dataserver/commsDebug.py index 8a0d4b46..0be4476c 100755 --- a/raspberry-dataserver/commsDebug.py +++ b/raspberry-dataserver/commsDebug.py @@ -25,16 +25,14 @@ dep = Dependant(comms) start = 0x1 stop = 0x2 -cmd = commandFormat() -cmd.cmdCode = start -cmd.toByteArray() +# initialise as start command, automatically executes toByteArray() +cmd = commandFormat(cmdCode=start) comms.writePayload(cmd) #comms.sender() while True: time.sleep(30) - cmd.cmdCode = stop - cmd.toByteArray() + cmd.cmdCode = stop # automatically executes toByteArray() comms.writePayload(cmd) #comms.registerData(stop) pass diff --git a/raspberry-dataserver/hevclient.py b/raspberry-dataserver/hevclient.py index e13d744f..cec47174 100755 --- a/raspberry-dataserver/hevclient.py +++ b/raspberry-dataserver/hevclient.py @@ -10,7 +10,7 @@ import threading from typing import List, Dict import logging logging.basicConfig(level=logging.INFO, - format='hevclient %(asctime)s - %(levelname)s - %(message)s') + format='%(asctime)s - %(levelname)s - %(message)s') polling = True setflag = False @@ -38,11 +38,10 @@ class HEVClient(object): # grab data from the socket as soon as it is available and dump it in the db while self._polling: data = await reader.read(500) - data = data.decode("utf-8") - data = json.loads(data) + payload = json.loads(data.decode("utf-8")) with self._lock: - self._values = data["sensors"] - self._alarms = data["alarms"] + self._values = payload["sensors"] + self._alarms = payload["alarms"] # close connection writer.close() @@ -51,15 +50,19 @@ class HEVClient(object): def start_client(self) -> None: asyncio.run(self.polling()) - async def send_request(self, type: str, mode: str = None, thresholds: List[float] = None) -> bool: + async def send_request(self, cmd: str, param: str=None) -> bool: # open connection and send packet reader, writer = await asyncio.open_connection("127.0.0.1", 54321) - payloads = { - "setmode": f"""{{"type": "setmode", "mode": \"{mode}\" }}""", - "setthresholds": f"""{{"type": "setthresholds", "thresholds": \"{thresholds}\"}}""", - "setup": f"""{{"type": "setup", "mode": \"{mode}\", "thresholds": \"{thresholds}\"}}""" + + payload = { + "type": "cmd", + "cmd": cmd, + "param": param } - writer.write(payloads[type].encode()) + + packet = json.dumps(payload).encode() + + writer.write(packet) await writer.drain() # wait for acknowledge @@ -71,31 +74,16 @@ class HEVClient(object): await writer.wait_closed() # check that acknowledge is meaningful - if type == "setmode" and data["type"] == "ackmode": - logging.info(f"Mode {mode} set successfully") - return True - if type == "setthresholds" and data["type"] == "ackthresholds": - logging.info(f"Thresholds {thresholds} set successfully") - return True - if type == "setup" and data["type"] == "ack": - logging.info( - f"Mode {mode} and thresholds {thresholds} set successfully") + if data["type"] == "ack": + logging.info(f"Command {cmd} sent successfully") return True else: - logging.warning(f'Setting {type} failed') + logging.warning(f"Sending command {cmd} failed") return False - def set_mode(self, mode: str) -> bool: - # set a mode and wait for acknowledge - return asyncio.run(self.send_request("setmode", mode=mode)) - - def set_thresholds(self, thresholds: List[float]) -> bool: - # set a threshold and wait for acknowledge - return asyncio.run(self.send_request("setthresholds", thresholds=thresholds)) - - def setup(self, mode: str, thresholds: List[float]) -> bool: - # set a mode and thresholds - return asyncio.run(self.send_request("setup", mode=mode, thresholds=thresholds)) + def send_cmd(self, cmd: str, param: str=None) -> bool: + # send a cmd and wait to see if it's valid + return asyncio.run(self.send_request(cmd)) def get_values(self) -> Dict: # get sensor values from db @@ -111,6 +99,7 @@ if __name__ == "__main__": # just import hevclient and do something like the following hevclient = HEVClient() + # Play with sensor values and alarms for i in range(30): values = hevclient.get_values() # returns a dict or None @@ -122,12 +111,11 @@ if __name__ == "__main__": print(f"Alarms: {alarms}") time.sleep(1) - # set modes and thresholds (this will change) - print(hevclient.set_mode("CPAP")) - print(hevclient.set_thresholds([12.3, 45.6, 78.9])) - print(hevclient.setup("CPAP", [12.3, 45.6, 78.9])) - print("The next one should fail as it is not a valid mode:") - print(hevclient.set_mode("foo")) + # send commands: + print(hevclient.send_cmd("CMD_START")) + time.sleep(1) + print("This one will fail since foo is not in the command_codes enum:") + print(hevclient.send_cmd("foo")) # print some more values for i in range(10): diff --git a/raspberry-dataserver/hevserver.py b/raspberry-dataserver/hevserver.py index 659d906c..83305a49 100755 --- a/raspberry-dataserver/hevserver.py +++ b/raspberry-dataserver/hevserver.py @@ -10,14 +10,16 @@ import threading import argparse import svpi import commsControl -from commsConstants import payloadType +from commsConstants import payloadType, command_codes, alarm_codes, commandFormat from collections import deque from serial.tools import list_ports from typing import List import logging -logging.basicConfig(level=logging.DEBUG, +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +class HEVPacketError(Exception): + pass class HEVServer(object): def __init__(self, lli): @@ -80,47 +82,43 @@ class HEVServer(object): # listen for queries on the request socket data = await reader.read(300) request = json.loads(data.decode("utf-8")) + + # logging addr = writer.get_extra_info("peername") logging.info(f"Answering request from {addr}") - payload = "" - - # three possible queries: set mode, set thresholds or both - if request["type"] == "setmode": - mode = request["mode"] - logging.debug(f"{addr!r} requested to change to mode {mode!r}") - - # send via protocol and prepare reply - if self._lli.setMode(mode): - packet = f"""{{"type": "ackmode", "mode": \"{mode}\"}}""".encode() - else: - packet = f"""{{"type": "nack"}}""".encode() - elif request["type"] == "setthresholds": - thresholds = request["thresholds"] - logging.debug( - f"{addr!r} requested to set thresholds to {thresholds!r}") - - # send via protocol - payload = self._lli.setThresholds(thresholds) - # prepare reply - packet = f"""{{"type": "ackthresholds", "thresholds": \"{payload}\"}}""".encode() - elif request["type"] == "setup": - mode = request["mode"] - thresholds = request["thresholds"] - logging.debug(f"{addr!r} requested to change to mode {mode!r}") - logging.debug( - f"{addr!r} requested to set thresholds to {thresholds!r}") - - # send via protocol and prepare reply - if self._lli.setMode(mode): - self._lli.setThresholds(thresholds) - packet = f"""{{"type": "ack", "mode": \"{mode}\", "thresholds": \"{thresholds}\"}}""".encode() - else: - packet = f"""{{"type": "nack"}}""".encode() - - # send reply and close connection - writer.write(packet) - await writer.drain() - writer.close() + + try: + reqtype = request["type"] + if reqtype == "cmd": + reqcmd = request["cmd"] + reqparam = request["param"] if request["param"] is not None else 0 + + if reqcmd in command_codes.__members__: + # valid request + command = commandFormat(cmdCode=command_codes[reqcmd].value, param=reqparam) + + self._lli.writePayload(command) + + # processed and sent to controller, send ack to GUI since it's in enum + # TODO should we wait for ack from controller or is that going to block the port for too long? + payload = {"type": "ack"} + else: + raise HEVPacketError("Invalid command packet") + + packet = json.dumps(payload).encode() + + # send reply and close connection + writer.write(packet) + await writer.drain() + writer.close() + except (NameError, HEVPacketError) as e: + # invalid request: reject immediately + logging.warning(f"Invalid command packet. Type {reqtype} does not exist") + payload = {"type": "nack"} + packet = json.dumps(payload).encode() + writer.write(packet) + await writer.drain() + writer.close() async def handle_broadcast(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: # log address @@ -142,7 +140,7 @@ class HEVServer(object): broadcast_packet["sensors"] = values broadcast_packet["alarms"] = alarms # add alarms key/value pair - logging.info(f"Send: {json.dumps(broadcast_packet,indent=4)}") + logging.debug(f"Send: {json.dumps(broadcast_packet,indent=4)}") try: writer.write(json.dumps(broadcast_packet).encode()) -- GitLab