Skip to content
Snippets Groups Projects
Commit d2ebfd9b authored by Dónal Murray's avatar Dónal Murray
Browse files

Add receiver logic on python side to deal with new data types, switch...

Add receiver logic on python side to deal with new data types, switch dataformat classes to dataclasses
parent efa416ee
Branches
No related merge requests found
......@@ -124,9 +124,10 @@ struct alarm_format {
#pragma pack()
enum DATA_TYPE: uint8_t {
FAST = 1,
READBACK = 2,
CYCLE = 3
FAST = 1,
READBACK = 2,
CYCLE = 3,
THRESHOLDS = 4
};
// struct for all data sent
......
from struct import Struct
from enum import Enum, auto, unique
from dataclasses import dataclass, asdict, field, fields
from typing import Any, ClassVar, Dict
import logging
import binascii
logging.basicConfig(level=logging.INFO,
......@@ -10,323 +12,300 @@ logging.basicConfig(level=logging.INFO,
# i.e. if and of DataFormat, CommandFormat or AlarmFormat change
# then change the RPI_VERSION
class PAYLOAD_TYPE(Enum):
DATA = auto()
READBACK = auto()
CYCLE = auto()
THRESHOLDS = auto()
CMD = auto()
ALARM = auto()
UNSET = auto()
@dataclass
class BaseFormat():
def __init__(self):
self._RPI_VERSION = 0xA2
self._byteArray = None
self._type = PAYLOAD_TYPE.UNSET
self._version = 0
@property
def byteArray(self):
return self._byteArray
# class variables excluded from init args and output dict
_RPI_VERSION: ClassVar[int] = field(default=0xA2, init=False, repr=False)
_type: ClassVar[Any] = field(default=PAYLOAD_TYPE.UNSET, init=False, repr=False)
_dataStruct: ClassVar[Any] = field(default=Struct("<BI"), init=False, repr=False)
_byteArray: ClassVar[bytearray] = field(default=None, init=False, repr=False)
# class variables
version: int = 0
timestamp: int = 0
@byteArray.setter
def byteArray(self):
print("Use fromByteArray to change this")
# check for mismatch between pi and microcontroller version
def checkVersion(self):
if self._RPI_VERSION == self._version :
self._version_error = True
else :
self._version_error = False
def checkVersion(self) -> bool:
return self._RPI_VERSION == self.version
def getSize(self):
def getSize(self) -> int:
return len(self._byteArray)
def getType(self):
return self._type
def getType(self) -> Any:
return self._type
def getDict(self) -> Dict:
return asdict(self)
# at the minute not generalised. needs to be overridden
def fromByteArray(self, byteArray) -> None:
(self.version,
self.timestamp) = self._dataStruct.unpack(byteArray)
self._byteArray = byteArray
# generalised
def toByteArray(self):
self.version = self._RPI_VERSION
loaddict = asdict(self)
load = [ loaddict[key] for key in loaddict ]
self._byteArray = self._dataStruct.pack(*load)
# =======================================
# data type payload
# fast data payload
# =======================================
@dataclass
class DataFormat(BaseFormat):
# subclass dataformat
_dataStruct = Struct("<BIBBHHHHHHHHHHHIII")
_type = PAYLOAD_TYPE.DATA
# subclass member variables
data_type: int = 1
fsm_state: str = "IDLE"
pressure_air_supply: int = 0
pressure_air_regulated: int = 0
pressure_o2_supply: int = 0
pressure_o2_regulated: int = 0
pressure_buffer: int = 0
pressure_inhale: int = 0
pressure_patient: int = 0
temperature_buffer: int = 0
pressure_diff_patient: int = 0
ambient_pressure: int = 0
ambient_temperature: int = 0
airway_pressure: float = 0
flow: float = 0
volume: float = 0
# define the format here, including version
def __init__(self):
super().__init__()
# struct will set the num bytes per variable
# B = unsigned char = 1 byte
# H = unsigned short = 2 bytes
# I = unsigned int = 4 bytes
# < = little endian
# > = big endian
# ! = network format (big endian)
self._dataStruct = Struct("<BIBBHHHHHHHHHHHIII")
self._byteArray = None
self._type = PAYLOAD_TYPE.DATA
# make all zero to start with
self._version = 0;
self._timestamp = 0;
self._data_type = 0;
self._fsm_state = "IDLE";
self._pressure_air_supply = 0;
self._pressure_air_regulated = 0;
self._pressure_o2_supply = 0;
self._pressure_o2_regulated = 0;
self._pressure_buffer = 0;
self._pressure_inhale = 0;
self._pressure_patient = 0;
self._temperature_buffer = 0;
self._pressure_diff_patient = 0;
self._ambient_pressure = 0;
self._ambient_temperature = 0;
self._airway_pressure = 0;
self._flow = 0;
self._volume = 0;
def __repr__(self):
return f"""{{
"version" : {self._version },
"timestamp" : {self._timestamp },
"data_type" : {self._data_type },
"fsm_state" : {self._fsm_state },
"pressure_air_supply" : {self._pressure_air_supply },
"pressure_air_regulated" : {self._pressure_air_regulated},
"pressure_o2_supply" : {self._pressure_o2_supply },
"pressure_o2_regulated" : {self._pressure_o2_regulated },
"pressure_buffer" : {self._pressure_buffer },
"pressure_inhale" : {self._pressure_inhale },
"pressure_patient" : {self._pressure_patient },
"temperature_buffer" : {self._temperature_buffer },
"pressure_diff_patient" : {self._pressure_diff_patient },
"ambient_pressure" : {self._ambient_pressure },
"ambient_temperature" : {self._ambient_temperature },
"airway_pressure" : {self._airway_pressure },
"flow" : {self._flow },
"volume" : {self._volume }
}}"""
# for receiving DataFormat from microcontroller
# fill the struct from a byteArray,
def fromByteArray(self, byteArray):
self._byteArray = byteArray
#logging.info(f"bytearray size {len(byteArray)} ")
#logging.info(binascii.hexlify(byteArray))
(self._version ,
self._timestamp ,
self._data_type ,
self._fsm_state ,
self._pressure_air_supply ,
self._pressure_air_regulated,
self._pressure_o2_supply ,
self._pressure_o2_regulated ,
self._pressure_buffer ,
self._pressure_inhale ,
self._pressure_patient ,
self._temperature_buffer ,
self._pressure_diff_patient ,
self._ambient_pressure ,
self._ambient_temperature ,
self._airway_pressure ,
self._flow ,
self._volume ) = self._dataStruct.unpack(self._byteArray)
tmp_state = 0
(self.version,
self.timestamp,
_, # dummy to skip datatype
tmp_state,
self.pressure_air_supply,
self.pressure_air_regulated,
self.pressure_o2_supply,
self.pressure_o2_regulated,
self.pressure_buffer,
self.pressure_inhale,
self.pressure_patient,
self.temperature_buffer,
self.pressure_diff_patient,
self.ambient_pressure,
self.ambient_temperature,
self.airway_pressure,
self.flow,
self.volume) = self._dataStruct.unpack(byteArray)
try:
self._fsm_state = BL_STATES(self._fsm_state).name
except ValueError:
self._fsm_state = BL_STATES(1).name
self.fsm_state = BL_STATES(tmp_state).name
except Exception:
# no longer silently die, catch Exceptions higher up
raise
self._byteArray = byteArray
# for sending DataFormat to microcontroller
# this is for completeness. Probably we never send this data
# to the microcontroller
def toByteArray(self):
# since pi is sender
self._version = self._RPI_VERSION
self._byteArray = self._dataStruct.pack(
self._RPI_VERSION ,
self._timestamp ,
self._data_type ,
self._fsm_state ,
self._pressure_air_supply ,
self._pressure_air_regulated,
self._pressure_o2_supply ,
self._pressure_o2_regulated ,
self._pressure_buffer ,
self._pressure_inhale ,
self._pressure_patient ,
self._temperature_buffer ,
self._pressure_diff_patient ,
self._ambient_pressure ,
self._ambient_temperature ,
self._airway_pressure ,
self._flow ,
self._volume
)
def getDict(self):
data = {
"version" : self._version ,
"timestamp" : self._timestamp ,
"data_type" : self._data_type ,
"fsm_state" : self._fsm_state ,
"pressure_air_supply" : self._pressure_air_supply ,
"pressure_air_regulated" : self._pressure_air_regulated,
"pressure_o2_supply" : self._pressure_o2_supply ,
"pressure_o2_regulated" : self._pressure_o2_regulated ,
"pressure_buffer" : self._pressure_buffer ,
"pressure_inhale" : self._pressure_inhale ,
"pressure_patient" : self._pressure_patient ,
"temperature_buffer" : self._temperature_buffer ,
"pressure_diff_patient" : self._pressure_diff_patient ,
"ambient_pressure" : self._ambient_pressure ,
"ambient_temperature" : self._ambient_temperature ,
"airway_pressure" : self._airway_pressure ,
"flow" : self._flow ,
"volume" : self._volume
}
return data
# =======================================
# readback data payload
# =======================================
@dataclass
class ReadbackFormat(BaseFormat):
_dataStruct = Struct("<BIBHHHHHHHHHHHBBBBBBBBBBBBBBI")
_type = PAYLOAD_TYPE.READBACK
data_type: int = 2
duration_calibration: int = 0
duration_buff_purge: int = 0
duration_buff_flush: int = 0
duration_buff_prefill: int = 0
duration_buff_fill: int = 0
duration_buff_loaded: int = 0
duration_buff_pre_inhale: int = 0
duration_inhale: int = 0
duration_pause: int = 0
duration_exhale_fill: int = 0
duration_exhale: int = 0
valve_air_in: int = 0
valve_o2_in: int = 0
valve_inhale: int = 0
valve_exhale: int = 0
valve_purge: int = 0
ventilation_mode: int = 0
valve_inhale_percent: int = 0
valve_exhale_percent: int = 0
valve_air_in_enable: int = 0
valve_o2_in_enable: int = 0
valve_purge_enable: int = 0
inhale_trigger_enable: int = 0
exhale_trigger_enable: int = 0
peep: int = 0
inhale_exhate_ratio: float = 0.0
# for receiving DataFormat from microcontroller
# fill the struct from a byteArray,
def fromByteArray(self, byteArray):
#logging.info(f"bytearray size {len(byteArray)} ")
#logging.info(binascii.hexlify(byteArray))
(self.version,
self.timestamp,
_, # dummy to skip datatype
self.duration_calibration,
self.duration_buff_purge,
self.duration_buff_flush,
self.duration_buff_prefill,
self.duration_buff_fill,
self.duration_buff_loaded,
self.duration_buff_pre_inhale,
self.duration_inhale,
self.duration_pause,
self.duration_exhale_fill,
self.duration_exhale,
self.valve_air_in,
self.valve_o2_in,
self.valve_inhale,
self.valve_exhale,
self.valve_purge,
self.ventilation_mode,
self.valve_inhale_percent,
self.valve_exhale_percent,
self.valve_air_in_enable,
self.valve_o2_in_enable,
self.valve_purge_enable,
self.inhale_trigger_enable,
self.exhale_trigger_enable,
self.peep,
self.inhale_exhate_ratio) = self._dataStruct.unpack(byteArray)
self._byteArray = byteArray
# =======================================
# cycle data payload
# =======================================
@dataclass
class CycleFormat(BaseFormat):
# subclass dataformat
_dataStruct = Struct("<BIBIIIIIIIIIHHHHBHHB")
_type = PAYLOAD_TYPE.CYCLE
data_type: int = 3
respiratory_rate: float = 0.0
tidal_volume: float = 0.0
exhaled_tidal_volume: float = 0.0
inhaled_tidal_volume: float = 0.0
minute_volume: float = 0.0
exhaled_minute_volume: float = 0.0
inhaled_minute_volume: float = 0.0
lung_compliance: float = 0.0
static_compliance: float = 0.0
inhalation_pressure: int = 0
peak_inspiratory_pressure: int = 0
plateau_pressure: int = 0
mean_airway_pressure: int = 0
fi02_percent: int = 0
apnea_index: int = 0
apnea_time: int = 0
mandatory_breath: int = 0
# for receiving DataFormat from microcontroller
# fill the struct from a byteArray,
def fromByteArray(self, byteArray):
#logging.info(f"bytearray size {len(byteArray)} ")
#logging.info(binascii.hexlify(byteArray))
(self.version,
self.timestamp,
_, # dummy to skip datatype
self.respiratory_rate,
self.tidal_volume,
self.exhaled_tidal_volume,
self.inhaled_tidal_volume,
self.minute_volume,
self.exhaled_minute_volume,
self.inhaled_minute_volume,
self.lung_compliance,
self.static_compliance,
self.inhalation_pressure,
self.peak_inspiratory_pressure,
self.plateau_pressure,
self.mean_airway_pressure,
self.fi02_percent,
self.apnea_index,
self.apnea_time,
self.mandatory_breath) = self._dataStruct.unpack(byteArray)
self._byteArray = byteArray
# =======================================
# thresholds eata payload
# =======================================
# TODO
# =======================================
# cmd type payload
# =======================================
@dataclass
class CommandFormat(BaseFormat):
def __init__(self, cmd_type=0, cmd_code=0, param=0):
super().__init__()
self._dataStruct = Struct("<BIBBI")
self._byteArray = None
self._type = PAYLOAD_TYPE.CMD
self._version = 0
self._timestamp = 0
self._cmd_type = cmd_type
self._cmd_code = cmd_code
self._param = param
self.toByteArray()
_dataStruct = Struct("<BIBBI")
_type = PAYLOAD_TYPE.CMD
# manage direct reading and writing of member variables
@property
def cmd_type(self):
return self._cmd_type
@cmd_type.setter
def cmd_type(self, cmd_typeIn):
self._cmd_type = cmd_typeIn
self.toByteArray()
cmd_type: int = 0
cmd_code: int = 0
param: int = 0
@property
def cmd_code(self):
return self._cmd_code
@cmd_code.setter
def cmd_code(self, cmd_codeIn):
self._cmd_code = cmd_codeIn
self.toByteArray()
@property
def param(self):
return self._param
@param.setter
def param(self, paramIn):
self._param = paramIn
def __post_init__(self):
self.toByteArray()
# print nicely
def __repr__(self):
return f"""{{
"version" : {self._version },
"timestamp" : {self._timestamp},
"cmd_type" : {self._cmd_type },
"cmd_code" : {self._cmd_code },
"param" : {self._param }
}}"""
def fromByteArray(self, byteArray):
(self.version,
self.timestamp,
self.cmd_type,
self.cmd_code,
self.param) = self._dataStruct.unpack(byteArray)
self._byteArray = byteArray
(self._version,
self._timestamp,
self._cmd_type,
self._cmd_code,
self._param) = self._dataStruct.unpack(self._byteArray)
def toByteArray(self):
# since pi is sender
self._byteArray = self._dataStruct.pack(
self._RPI_VERSION,
self._timestamp ,
self._cmd_type ,
self._cmd_code ,
self._param
)
def getDict(self):
data = {
"version" : self._version ,
"timestamp" : self._timestamp,
"cmd_type" : self._cmd_type ,
"cmd_code" : self._cmd_code ,
"param" : self._param
}
return data
# =======================================
# alarm type payload
# =======================================
@dataclass
class AlarmFormat(BaseFormat):
def __init__(self):
super().__init__()
self._dataStruct = Struct("<BIBBI")
self._byteArray = None
self._type = PAYLOAD_TYPE.ALARM
self._version = 0
self._timestamp = 0
self._alarm_type = 0
self._alarm_code = 0
self._param = 0
def __repr__(self):
return f"""{{
"version" : {self._version },
"timestamp" : {self._timestamp },
"alarm_type" : {self._alarm_type},
"alarm_code" : {self._alarm_code},
"param" : {self._param }
}}"""
_dataStruct = Struct("<BIBBI")
_type = PAYLOAD_TYPE.ALARM
alarm_type: int = 0
alarm_code: int = 0
param: int = 0
def fromByteArray(self, byteArray):
alarm = 0
(self.version,
self.timestamp,
self.alarm_type,
alarm,
self.param) = self._dataStruct.unpack(byteArray)
self.alarm_code = ALARM_CODES(alarm).name
self._byteArray = byteArray
(self._version ,
self._timestamp ,
self._alarm_type,
self._alarm_code,
self._param) = self._dataStruct.unpack(self._byteArray)
def toByteArray(self):
self._byteArray = self._dataStruct.pack(
self._RPI_VERSION,
self._timestamp ,
self._alarm_type ,
self._alarm_code ,
self._param
)
def getDict(self):
data = {
"version" : self._version ,
"timestamp" : self._timestamp ,
"alarm_type" : self._alarm_type,
"alarm_code" : self._alarm_code,
"param" : self._param
}
return data
# =======================================
# Enum definitions
# =======================================
class PAYLOAD_TYPE(Enum):
DATA = auto()
CMD = auto()
ALARM = auto()
UNSET = auto()
@unique
class CMD_TYPE(Enum):
GENERAL = 1
......@@ -375,7 +354,7 @@ class ALARM_CODES(Enum):
CHECK_VALVE_EXHALE = 2 # HP
CHECK_P_PATIENT = 3 # HP
EXPIRATION_SENSE_FAULT_OR_LEAK = 4 # MP
EXPIRATION_VALVE_Leak = 5 # MP
EXPIRATION_VALVE_LEAK = 5 # MP
HIGH_FIO2 = 6 # MP
HIGH_PRESSURE = 7 # HP
HIGH_RR = 8 # MP
......
......@@ -218,7 +218,7 @@ class CommsControl():
except:
logging.debug("Queue is probably empty")
def receivePacket(self, payload_type, commsPacket):
def receivePacket(self, payload_type, comms_packet):
if payload_type == CommsCommon.PAYLOAD_TYPE.ALARM:
payload = CommsCommon.AlarmFormat()
elif payload_type == CommsCommon.PAYLOAD_TYPE.CMD:
......@@ -228,9 +228,14 @@ class CommsControl():
else:
return False
payload.fromByteArray(commsPacket.getData()[commsPacket.getInformation():commsPacket.getFcs()])
self.payloadrecv = payload
return True
try:
payload.fromByteArray(comms_packet.getData()[comms_packet.getInformation():comms_packet.getFcs()])
except Exception:
raise
else:
self.payloadrecv = payload
return True
return False
# escape any 0x7D or 0x7E with 0x7D and swap bit 5
def escapeByte(self, byte):
......@@ -298,7 +303,7 @@ if __name__ == "__main__" :
def update_llipacket(self, payload):
if payload.getType() == CommsCommon.PAYLOAD_TYPE.DATA:
logging.info(f"payload received: {payload._fsm_state}")
logging.info(f"payload received: {payload.fsm_state}")
self._llipacket = payload
# pop from queue - protects against Dependant going down and not receiving packets
......
......@@ -17,8 +17,8 @@ class Dependant(object):
def update_llipacket(self, payload):
# logging.info(f"payload received: {payload}")
logging.info(f"payload received: {payload._fsm_state}")
#logging.info(f"payload received: {payload._readback_valve_o2_in} {payload._readback_valve_inhale} {payload._readback_valve_exhale} {payload._readback_valve_purge} {payload._fsm_state}")
logging.info(f"payload received: {payload.fsm_state}")
#logging.info(f"payload received: {payload.readback_valve_o2_in} {payload.readback_valve_inhale} {payload.readback_valve_exhale} {payload.readback_valve_purge} {payload.fsm_state}")
self._llipacket = payload.getDict() # returns a dict
# pop from queue - protects against Dependant going down and not receiving packets
self._lli.pop_payloadrecv()
......@@ -33,7 +33,8 @@ comms.writePayload(cmd)
print('sent cmd start')
while True:
time.sleep(20)
cmd.cmd_code = CMD_GENERAL.STOP.value # automatically executes toByteArray()
cmd.cmd_code = CMD_GENERAL.STOP.value # no longer automatically executes toByteArray() !
cmd.toByteArray()
comms.writePayload(cmd)
print('sent cmd stop')
pass
......
......@@ -22,6 +22,9 @@ class HEVClient(object):
def __init__(self):
self._alarms = [] # db for alarms
self._values = None # db for sensor values
self._readback = None # db for sensor values
self._cycle = None # db for sensor values
self._thresholds = None # db for sensor values
self._thresholds = [] # db for threshold settings
self._polling = True # keep reading data into db
self._lock = threading.Lock() # lock for the database
......@@ -37,21 +40,36 @@ class HEVClient(object):
# grab data from the socket as soon as it is available and dump it in the db
while self._polling:
try:
data = await reader.read(600)
if data[-1] == 0x00:
data = data[:-1]
data = b''
while True:
data += await reader.read(600)
if data[-1] == 0x00:
data = data[:-1]
break
payload = json.loads(data.decode("utf-8"))
if payload["type"] == "broadcast":
with self._lock:
self._values = payload["sensors"]
self._alarms = payload["alarms"]
elif payload["type"] == "keepalive":
if payload["type"] == "keepalive":
#Still alive
pass
continue
elif payload["type"] == "DATA":
with self._lock:
self._values = payload["DATA"]
elif payload["type"] == "READBACK":
with self._lock:
self._readback = payload["READBACK"]
elif payload["type"] == "CYCLE":
with self._lock:
self._cycle = payload["CYCLE"]
elif payload["type"] == "THRESHOLDS":
with self._lock:
self._thresholds = payload["THRESHOLDS"]
else:
raise HEVPacketError(f"Invalid packet type: {payload['type']}")
raise HEVPacketError("Invalid broadcast type")
self._alarms = payload["alarms"]
except json.decoder.JSONDecodeError:
logging.warning(f"Could not decode packet: {data}")
except KeyError:
raise
# close connection
writer.close()
......@@ -149,11 +167,6 @@ if __name__ == "__main__":
time.sleep(2)
print(f"Alarms: {hevclient.get_alarms()}")
# send commands:
time.sleep(1)
print("This one will fail since foo is not in the CMD_GENERAL enum:")
print(hevclient.send_cmd("GENERAL", "foo"))
# print some more values
for i in range(10):
print(f"Values: {json.dumps(hevclient.get_values(), indent=4)}")
......
......@@ -38,11 +38,11 @@ class hevfromtxt():
while True:
# directly setting private member variables in this edge case
payload = CommsCommon.DataFormat()
payload._version = payload._RPI_VERSION
payload._timestamp = time_offset + self._timestamp[self._pos]
payload._pressure_buffer = self._pressure[self._pos]
payload._pressure_inhale = self._volume[self._pos]
payload._temperature_buffer = self._flow[self._pos]
payload.version = payload._RPI_VERSION
payload.timestamp = time_offset + self._timestamp[self._pos]
payload.pressure_buffer = self._pressure[self._pos]
payload.pressure_inhale = self._volume[self._pos]
payload.temperature_buffer = self._flow[self._pos]
self.payloadrecv = payload
if self._pos + self._increment < self._length:
......
......@@ -11,10 +11,11 @@ import argparse
import svpi
import hevfromtxt
import CommsControl
from CommsCommon import PAYLOAD_TYPE, CMD_TYPE, CMD_GENERAL, CMD_SET_TIMEOUT, CMD_SET_MODE, ALARM_CODES, CMD_MAP, CommandFormat
from CommsCommon import PAYLOAD_TYPE, CMD_TYPE, CMD_GENERAL, CMD_SET_TIMEOUT, CMD_SET_MODE, ALARM_CODES, CMD_MAP, CommandFormat, AlarmFormat
from collections import deque
from serial.tools import list_ports
from typing import List
from struct import error as StructError
import logging
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s')
logging.getLogger().setLevel(logging.INFO)
......@@ -38,10 +39,6 @@ class HEVServer(object):
worker = threading.Thread(target=self.serve_all, daemon=True)
worker.start()
def __repr__(self):
with self._dblock:
return f"Alarms: {self._alarms}.\nSensor values: {self._values}"
def polling(self, payload):
# get values when we get a callback from commsControl (lli)
logging.debug(f"Payload received: {payload!r}")
......@@ -49,24 +46,16 @@ class HEVServer(object):
payload_type = payload.getType()
if payload_type == PAYLOAD_TYPE.ALARM:
# Alarm is latched until acknowledged in GUI
alarm_packet = payload.getDict()
alarm_code = alarm_packet["alarm_code"]
with self._dblock:
try:
alarm = ALARM_CODES(alarm_code).name
if alarm not in self._alarms:
self._alarms.append(alarm)
except ValueError as e:
# alarmCode does not exist in the enum, this is serious!
logging.error(e)
self._alarms.append("ARDUINO_FAIL") # assume Arduino is broken
if payload not in self._alarms:
self._alarms.append(payload)
# let broadcast thread know there is data to send
with self._dvlock:
self._datavalid.set()
elif payload_type == PAYLOAD_TYPE.DATA:
# pass data to db
with self._dblock:
self._values = payload.getDict()
self._values = payload
# let broadcast thread know there is data to send
with self._dvlock:
self._datavalid.set()
......@@ -114,15 +103,22 @@ class HEVServer(object):
# processed and sent to controller, send ack to GUI since it's in enum
payload = {"type": "ack"}
elif reqtype == "broadcast":
elif reqtype == "DATA":
# ignore for the minute
pass
elif reqtype == "READBACK":
# ignore for the minute
pass
elif reqtype == "CYCLE":
# ignore for the minute
pass
elif reqtype == "alarm":
# acknowledgement of alarm from gui
alarm_to_ack = AlarmFormat(**request["ack"])
try:
# delete alarm if it exists
with self._dblock:
self._alarms.remove(request["ack"])
self._alarms.remove(alarm_to_ack)
payload = {"type": "ack"}
except NameError as e:
raise HEVPacketError(f"Alarm could not be removed. May have been removed already. {e}")
......@@ -164,14 +160,16 @@ class HEVServer(object):
self._broadcasting = False
continue
else:
broadcast_packet = {"type": "broadcast"}
broadcast_packet["sensors"] = values
broadcast_packet["alarms"] = alarms # add alarms key/value pair
data_type = values.getType().name
broadcast_packet = {"type": data_type}
broadcast_packet[str(data_type)] = values.getDict()
broadcast_packet["alarms"] = [alarm.getDict() for alarm in alarms] if alarms is not None else []
# take control of datavalid and reset it
with self._dvlock:
self._datavalid.clear()
logging.info(f"Send data for timestamp: {broadcast_packet['sensors']['timestamp']}")
logging.info(f"Send data for timestamp: {broadcast_packet[data_type]['timestamp']}")
logging.debug(f"Send: {json.dumps(broadcast_packet,indent=4)}")
try:
......@@ -224,49 +222,55 @@ class HEVServer(object):
if __name__ == "__main__":
#parser to allow us to pass arguments to hevserver
parser = argparse.ArgumentParser(description='Arguments to run hevserver')
parser.add_argument('--inputFile', type=str, default = '', help='a test file to load data')
parser.add_argument('-d', '--debug', action='store_true', help='Show debug output')
args = parser.parse_args()
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
try:
#parser to allow us to pass arguments to hevserver
parser = argparse.ArgumentParser(description='Arguments to run hevserver')
parser.add_argument('--inputFile', type=str, default = '', help='a test file to load data')
parser.add_argument('-d', '--debug', action='store_true', help='Show debug output')
args = parser.parse_args()
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
# check if input file was specified
if args.inputFile != '':
if args.inputFile[-1-3:] == '.txt':
# assume sample.txt format
lli = hevfromtxt.hevfromtxt(args.inputFile)
# check if input file was specified
if args.inputFile != '':
if args.inputFile[-1-3:] == '.txt':
# assume sample.txt format
lli = hevfromtxt.hevfromtxt(args.inputFile)
else:
# assume hex dump
lli = svpi.svpi(args.inputFile)
logging.info(f"Reading data from {args.inputFile}")
else:
# assume hex dump
lli = svpi.svpi(args.inputFile)
logging.info(f"Reading data from {args.inputFile}")
else:
# get arduino serial port
for port in list_ports.comports():
vidpid = ""
if port.pid != None and port.vid != None:
vidpid = f"{ port.vid:04x}:{port.pid:04x}".upper()
logging.debug(vidpid)
if port.manufacturer and "ARDUINO" in port.manufacturer.upper():
port_device = port.device
elif vidpid == "10C4:EA60" :
port_device = port.device
# initialise low level interface
try:
lli = CommsControl.CommsControl(port=port_device)
logging.info(f"Serving data from device {port_device}")
except NameError:
logging.error("Arduino not connected")
exit(1)
# get arduino serial port
for port in list_ports.comports():
vidpid = ""
if port.pid != None and port.vid != None:
vidpid = f"{ port.vid:04x}:{port.pid:04x}".upper()
logging.debug(vidpid)
if port.manufacturer and "ARDUINO" in port.manufacturer.upper():
port_device = port.device
elif vidpid == "10C4:EA60" :
port_device = port.device
# initialise low level interface
try:
lli = CommsControl.CommsControl(port=port_device)
logging.info(f"Serving data from device {port_device}")
except NameError:
logging.error("Arduino not connected")
exit(1)
hevsrv = HEVServer(lli)
hevsrv = HEVServer(lli)
# serve forever
loop = asyncio.get_event_loop()
try:
loop.run_forever()
finally:
loop.close()
except KeyboardInterrupt:
logging.info("Server stopped")
except StructError:
logging.error("Failed to parse packet")
# serve forever
loop = asyncio.get_event_loop()
try:
loop.run_forever()
finally:
loop.close()
......@@ -26,6 +26,7 @@ class svpi():
# received queue and observers to be notified on update
self._payloadrecv = deque(maxlen = 16)
self._observers = []
self._delay = 1
sendingWorker = threading.Thread(target=self.generate, daemon=True)
sendingWorker.start()
......@@ -39,15 +40,20 @@ class svpi():
else:
# grab next array from filedump
fullArray = self._bytestore[0+self._pos*27:27+self._pos*27]
# currently (20200420) the byte dump has the wrong format of 27 bytes, expects 30. snip out second byte and add four more bytes for zeroed timestamp
byteArray = fullArray[:1] + fullArray[-1-3:] + fullArray[2:]
# currently (20200426) the byte dump has the wrong format of 27 bytes, expects 41. snip out second byte and add four more bytes for zeroed timestamp
byteArray = b'\xa2' + fullArray[-1-3:] + bytearray((0x01,0x01)) + fullArray[2:] + fullArray[-1-8:]
# go to next byte array. if at the end, loop
self._pos = self._pos + 1 if self._pos < 99 else 0
payload = CommsCommon.DataFormat()
#try:
payload.fromByteArray(byteArray)
#except Exception as e:
# logging.error(f"Failed to parse packet: {e}")
#else:
self.payloadrecv = payload
time.sleep(1)
time.sleep(self._delay)
def getAlarms(self) -> List[str]:
# give/cancel a random alarm a twentieth of the time
......@@ -55,7 +61,7 @@ class svpi():
# send alarm
alarm = 1 + np.random.randint(0, len(ALARM_CODES))
# give all simulated alarms low priority for the minute
return bytearray((0xA0,0x00,0x00,0x00,0x00,0x01,alarm,0x00,0x00,0x00,0x00))
return bytearray((0xA2,0x00,0x00,0x00,0x00,0x01,alarm,0x00,0x00,0x00,0x00))
return None
# callback to dependants to read the received payload
......
#!/usr/bin/env bash
set -euo pipefail
for struct in data_format cmd_format alarm_format; do
echo $struct;
sed -e "/struct $struct/,/};/!d" -e "s/struct $struct.*/self._dataStruct = Struct(\"</" -e '/};/d' -e 's/enum \(.*\) {/class \1(Enum):/' -e 's/uint\(.*\)_t.*/\1/g' -e 's/.*8.*/B/g' -e 's/.*16.*/H/g' -e 's/.*32.*/I/g' ../arduino/common/lib/CommsControl/CommsCommon.h | sed -e ':a;N;$!ba;s/\n//g' -e 's/$/\")/';
for struct in fast_data_format readback_data_format cycle_data_format cmd_format alarm_format; do
structtype=${struct%%_*}
echo '@dataclass'
sed -e "/struct $struct/,/};/!d" -e "/struct $struct/!d" -e "s/struct .* {/class ${structtype^}Format(BaseFormat):/" ../arduino/hev_prototype_v1/src/common.h;
sed -e "/struct $struct/,/};/!d" -e "s/struct $struct.*/_dataStruct = Struct(\"</" -e '/};/d' -e 's/uint\(.*\)_t.*/\1/g' -e 's/.*8.*/B/g' -e 's/.*16.*/H/g' -e 's/.*32.*/I/g' -e 's/.*float.*/I/g' -e 's/\/\/.*//g' ../arduino/hev_prototype_v1/src/common.h | sed -e ':a;N;$!ba;s/\n//g' -e 's/^/ /' -e 's/$/\")/';
echo " _type = PAYLOAD_TYPE.${structtype^^}"
sed -e "/struct $struct/,/};/!d" -e "/struct $struct/d" -e '/};/d' -e 's/struct \(.*\) {/class \1(BaseFormat):/' -e 's/uint8_t //g' -e 's/uint16_t//g' -e 's/uint32_t//g' -e 's/float//g' -e 's/\/\/.*//g' -e 's/::/./g' -e 's/;$//' -e 's/^[ \t]*/ /' ../arduino/hev_prototype_v1/src/common.h;
echo
done
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment