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