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

Add latched alarms which have to be acknowledged by user before being removed

parent 82cc9943
Branches
No related merge requests found
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
#define PACKET_DATA 0x40 #define PACKET_DATA 0x40
#define PACKET_SET 0x20 //set vs get ? #define PACKET_SET 0x20 //set vs get ?
#define HEV_FORMAT_VERSION 0xA1 #define HEV_FORMAT_VERSION 0xA0
enum command_codes {CMD_START = 1, enum command_codes {CMD_START = 1,
CMD_STOP = 2}; CMD_STOP = 2};
......
...@@ -73,7 +73,7 @@ Example broadcast packet: ...@@ -73,7 +73,7 @@ Example broadcast packet:
"readback_mode": 0 "readback_mode": 0
}, },
"alarms": [ "alarms": [
"ALARM_START" "APNEA"
] ]
} }
``` ```
...@@ -82,7 +82,7 @@ Example alarm packet: ...@@ -82,7 +82,7 @@ Example alarm packet:
```json ```json
{ {
“type”: “alarm”, “type”: “alarm”,
“alarms”: [apnea] “alarms”: [APNEA]
} }
``` ```
...@@ -153,4 +153,4 @@ for i in range(10): ...@@ -153,4 +153,4 @@ for i in range(10):
print(f"Values: {json.dumps(hevclient.get_values(), indent=4)}") print(f"Values: {json.dumps(hevclient.get_values(), indent=4)}")
print(f"Alarms: {hevclient.get_alarms()}") print(f"Alarms: {hevclient.get_alarms()}")
time.sleep(1) time.sleep(1)
``` ```
\ No newline at end of file
...@@ -17,7 +17,7 @@ class payloadType(Enum): ...@@ -17,7 +17,7 @@ class payloadType(Enum):
class BaseFormat(): class BaseFormat():
def __init__(self): def __init__(self):
self._RPI_VERSION = 0xA1 self._RPI_VERSION = 0xA0
self._byteArray = None self._byteArray = None
self._type = payloadType.payloadUnset self._type = payloadType.payloadUnset
self._version = 0 self._version = 0
...@@ -294,7 +294,7 @@ class alarm_codes(Enum): ...@@ -294,7 +294,7 @@ class alarm_codes(Enum):
CHECK_VALVE_EXHALE = 2 # HP CHECK_VALVE_EXHALE = 2 # HP
CHECK_P_PATIENT = 3 # HP CHECK_P_PATIENT = 3 # HP
EXPIRATION_SENSE_FAULT_OR_LEAK = 4 # MP EXPIRATION_SENSE_FAULT_OR_LEAK = 4 # MP
EXPIRATION_VALVE_Leak = 5 # MP EXPIRATION_VALVE_LEAK = 5 # MP
HIGH_FIO2 = 6 # MP HIGH_FIO2 = 6 # MP
HIGH_PRESSURE = 7 # HP HIGH_PRESSURE = 7 # HP
HIGH_RR = 8 # MP HIGH_RR = 8 # MP
......
...@@ -37,8 +37,11 @@ class HEVClient(object): ...@@ -37,8 +37,11 @@ class HEVClient(object):
# grab data from the socket as soon as it is available and dump it in the db # grab data from the socket as soon as it is available and dump it in the db
while self._polling: while self._polling:
data = await reader.read(500) data = await reader.read(600)
payload = json.loads(data.decode("utf-8")) try:
payload = json.loads(data.decode("utf-8"))
except json.decoder.JSONDecodeError:
logging.warning(f"Could not decode packet: {data}")
with self._lock: with self._lock:
self._values = payload["sensors"] self._values = payload["sensors"]
self._alarms = payload["alarms"] self._alarms = payload["alarms"]
...@@ -50,15 +53,21 @@ class HEVClient(object): ...@@ -50,15 +53,21 @@ class HEVClient(object):
def start_client(self) -> None: def start_client(self) -> None:
asyncio.run(self.polling()) asyncio.run(self.polling())
async def send_request(self, cmd: str, param: str=None) -> bool: async def send_request(self, reqtype, cmd: str=None, param: str=None, alarm: str=None) -> bool:
# open connection and send packet # open connection and send packet
reader, writer = await asyncio.open_connection("127.0.0.1", 54321) reader, writer = await asyncio.open_connection("127.0.0.1", 54321)
payload = { if reqtype == "cmd":
"type": "cmd", payload = {
"cmd": cmd, "type": "cmd",
"param": param "cmd": cmd,
} "param": param
}
elif reqtype == "alarm":
payload = {
"type": "alarm",
"ack": alarm
}
packet = json.dumps(payload).encode() packet = json.dumps(payload).encode()
...@@ -67,7 +76,10 @@ class HEVClient(object): ...@@ -67,7 +76,10 @@ class HEVClient(object):
# wait for acknowledge # wait for acknowledge
data = await reader.read(300) data = await reader.read(300)
data = json.loads(data.decode("utf-8")) try:
data = json.loads(data.decode("utf-8"))
except json.decoder.JSONDecodeError:
logging.warning(f"Could not decode packet: {data}")
# close connection to free up socket # close connection to free up socket
writer.close() writer.close()
...@@ -75,15 +87,19 @@ class HEVClient(object): ...@@ -75,15 +87,19 @@ class HEVClient(object):
# check that acknowledge is meaningful # check that acknowledge is meaningful
if data["type"] == "ack": if data["type"] == "ack":
logging.info(f"Command {cmd} sent successfully") logging.info(f"Request type {reqtype} sent successfully")
return True return True
else: else:
logging.warning(f"Sending command {cmd} failed") logging.warning(f"Request type {reqtype} failed")
return False return False
def send_cmd(self, cmd: str, param: str=None) -> bool: def send_cmd(self, cmd: str, param: str=None) -> bool:
# send a cmd and wait to see if it's valid # send a cmd and wait to see if it's valid
return asyncio.run(self.send_request(cmd)) return asyncio.run(self.send_request("cmd", cmd=cmd, param=param))
def ack_alarm(self, alarm: str) -> bool:
# acknowledge alarm to remove it from the hevserver list
return asyncio.run(self.send_request("alarm", alarm=alarm))
def get_values(self) -> Dict: def get_values(self) -> Dict:
# get sensor values from db # get sensor values from db
...@@ -101,7 +117,7 @@ if __name__ == "__main__": ...@@ -101,7 +117,7 @@ if __name__ == "__main__":
# Play with sensor values and alarms # Play with sensor values and alarms
for i in range(30): for i in range(20):
values = hevclient.get_values() # returns a dict or None values = hevclient.get_values() # returns a dict or None
alarms = hevclient.get_alarms() # returns a list of alarms currently ongoing alarms = hevclient.get_alarms() # returns a list of alarms currently ongoing
if values is None: if values is None:
...@@ -110,6 +126,15 @@ if __name__ == "__main__": ...@@ -110,6 +126,15 @@ if __name__ == "__main__":
print(f"Values: {json.dumps(values, indent=4)}") print(f"Values: {json.dumps(values, indent=4)}")
print(f"Alarms: {alarms}") print(f"Alarms: {alarms}")
time.sleep(1) time.sleep(1)
# acknowledge the oldest alarm
try:
hevclient.ack_alarm(alarms[0]) # blindly assume we have one after 40s
except:
logging.info("No alarms received")
time.sleep(1)
print(f"Alarms: {hevclient.get_alarms()}")
# send commands: # send commands:
print(hevclient.send_cmd("CMD_START")) print(hevclient.send_cmd("CMD_START"))
...@@ -119,7 +144,6 @@ if __name__ == "__main__": ...@@ -119,7 +144,6 @@ if __name__ == "__main__":
# print some more values # print some more values
for i in range(10): for i in range(10):
print(f"Alarms: {hevclient.get_alarms()}")
print(f"Values: {json.dumps(hevclient.get_values(), indent=4)}") print(f"Values: {json.dumps(hevclient.get_values(), indent=4)}")
print(f"Alarms: {hevclient.get_alarms()}") print(f"Alarms: {hevclient.get_alarms()}")
time.sleep(1) time.sleep(1)
...@@ -47,34 +47,38 @@ class HEVServer(object): ...@@ -47,34 +47,38 @@ class HEVServer(object):
logging.debug(f"Payload received: {payload!r}") logging.debug(f"Payload received: {payload!r}")
# check if it is data or alarm # check if it is data or alarm
payload_type = payload.getType() payload_type = payload.getType()
if payload_type == payloadType.payloadData: if payload_type == payloadType.payloadAlarm:
# Alarm is latched until acknowledged in GUI
alarm_packet = payload.getDict()
alarmCode = alarm_packet["alarmCode"]
with self._dblock:
try:
alarm = alarm_codes(alarmCode).name
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 alarm not in self._alarms:
self._alarms.append(alarm)
# let broadcast thread know there is data to send
with self._dvlock:
self._datavalid.set()
elif payload_type == payloadType.payloadData:
# pass data to db # pass data to db
with self._dblock: with self._dblock:
self._values = payload.getDict() self._values = payload.getDict()
elif payload_type == payloadType.payloadAlarm: # let broadcast thread know there is data to send
alarm_map = { with self._dvlock:
0: "manual", self._datavalid.set()
1: "gas supply", elif payload_type == payloadType.payloadCmd:
2: "apnea", # ignore for the minute
3: "expired minute volume", pass
4: "upper pressure limit", elif payload_type == payloadType.payloadUnset:
5: "power failure", # ignore for the minute
} pass
new_alarm = payload.getDict() else:
param = new_alarm["param"] # invalid packet, don't throw exception just log and pop
if new_alarm["alarmCode"] == 2: logging.error("Received invalid packet, ignoring")
# alarm stop, delete from list
with self._dblock:
self._alarms.remove(alarm_map[param])
elif new_alarm["alarmCode"] == 1:
# alarm start, add to list
with self._dblock:
self._alarms.append(alarm_map[param])
# let broadcast thread know there is data to send
with self._dvlock:
self._datavalid.set()
# pop from lli queue # pop from lli queue
self._lli.pop_payloadrecv() self._lli.pop_payloadrecv()
...@@ -101,20 +105,36 @@ class HEVServer(object): ...@@ -101,20 +105,36 @@ class HEVServer(object):
self._lli.writePayload(command) self._lli.writePayload(command)
# processed and sent to controller, send ack to GUI since it's in enum # 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"} payload = {"type": "ack"}
else: else:
raise HEVPacketError("Invalid command packet") raise HEVPacketError(f"Invalid command packet. Command {reqcmd} does not exist.")
packet = json.dumps(payload).encode() elif reqtype == "broadcast":
# ignore for the minute
pass
elif reqtype == "alarm":
# acknowledgement of alarm from gui
try:
# delete alarm if it exists
with self._dblock:
self._alarms.remove(request["ack"])
payload = {"type": "ack"}
except NameError as e:
raise HEVPacketError(f"Alarm could not be removed. May have been removed already. {e}")
else:
raise HEVPacketError(f"Invalid request type")
packet = json.dumps(payload).encode()
# send reply and close connection
writer.write(packet)
await writer.drain()
writer.close()
# send reply and close connection
writer.write(packet)
await writer.drain()
writer.close()
except (NameError, HEVPacketError) as e: except (NameError, HEVPacketError) as e:
# invalid request: reject immediately # invalid request: reject immediately
logging.warning(f"Invalid command packet. Type {reqtype} does not exist") logging.warning(e)
payload = {"type": "nack"} payload = {"type": "nack"}
packet = json.dumps(payload).encode() packet = json.dumps(payload).encode()
writer.write(packet) writer.write(packet)
......
...@@ -10,6 +10,7 @@ from collections import deque ...@@ -10,6 +10,7 @@ from collections import deque
import commsFormat import commsFormat
import threading import threading
import commsConstants import commsConstants
from commsConstants import alarm_codes
from typing import List, Dict from typing import List, Dict
import logging import logging
logging.basicConfig(level=logging.INFO, logging.basicConfig(level=logging.INFO,
...@@ -22,7 +23,6 @@ class svpi(): ...@@ -22,7 +23,6 @@ class svpi():
# dump file to variable # dump file to variable
self._bytestore = bytearray(self._input.read()) self._bytestore = bytearray(self._input.read())
self._pos = 0 # position inside bytestore self._pos = 0 # position inside bytestore
self._currentAlarm = None
# received queue and observers to be notified on update # received queue and observers to be notified on update
self._payloadrecv = deque(maxlen = 16) self._payloadrecv = deque(maxlen = 16)
self._observers = [] self._observers = []
...@@ -51,46 +51,12 @@ class svpi(): ...@@ -51,46 +51,12 @@ class svpi():
def getAlarms(self) -> List[str]: def getAlarms(self) -> List[str]:
# give/cancel a random alarm a twentieth of the time # give/cancel a random alarm a twentieth of the time
#alarms = {
# 0: "manual",
# 1: "gas supply",
# 2: "apnea",
# 3: "expired minute volume",
# 4: "upper pressure limit",
# 5: "power failure",
#}
if np.random.randint(0, 20) == 0: if np.random.randint(0, 20) == 0:
if self._currentAlarm is None: # send alarm
# send alarm alarm = 1 + np.random.randint(0, len(alarm_codes))
alarm = np.random.randint(0, 6) return bytearray((0xA0,alarm,0x00,0x00,0x00,0x00))
self._currentAlarm = alarm
return bytearray((0xA0,0x01,alarm,0x00,0x00,0x00))
else:
# stop previous alarm
alarm = self._currentAlarm
self._currentAlarm = None
return bytearray((0xA0,0x02,alarm,0x00,0x00,0x00))
return None return None
def getThresholds(self) -> List[float]:
# All thresholds 32 bit floats
thresholds: List[float] = np.random.uniform(0.0, 1000.0, 3).tolist()
return thresholds
def setMode(self, mode: str) -> bool:
# setting a mode - just print it
if mode in ("PRVC", "SIMV-PC", "CPAP"):
logging.info(f"Setting mode {mode}")
return True
else:
logging.error(f"Requested mode {mode} does not exist")
return False
def setThresholds(self, thresholds: List[float]) -> str:
# setting thresholds - just print them
logging.info(f"Setting thresholds {thresholds}")
return thresholds
# callback to dependants to read the received payload # callback to dependants to read the received payload
@property @property
def payloadrecv(self): def payloadrecv(self):
......
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