diff --git a/arduino/common/lib/CommsControl/CommsCommon.h b/arduino/common/lib/CommsControl/CommsCommon.h
index 1d38defa1641ff8113cb23bc1d5bdad9f151a8ce..1a0ac7cf939514a4fc783d9f32cbece9e4e5c922 100644
--- a/arduino/common/lib/CommsControl/CommsCommon.h
+++ b/arduino/common/lib/CommsControl/CommsCommon.h
@@ -40,11 +40,11 @@
 #define PACKET_SET   0x20 //set vs get ?
 
 // enum of all transfer types
-enum PAYLOAD_TYPE {
-    DATA,
-    CMD,
-    ALARM,
-    UNSET
+enum PRIORITY : uint8_t {
+    DATA_ADDR  = PACKET_DATA,
+    CMD_ADDR   = PACKET_CMD,
+    ALARM_ADDR = PACKET_ALARM,
+    UNSET_ADDR = 0x00
 };
 
 // payload consists of type and information
@@ -52,7 +52,7 @@ enum PAYLOAD_TYPE {
 // information is set as information in the protocol
 class Payload {
 public:
-    Payload(PAYLOAD_TYPE type = PAYLOAD_TYPE::UNSET)  {_type = type; }
+    Payload(PRIORITY type = PRIORITY::UNSET_ADDR)  {_type = type; }
     Payload(const Payload &other) {
         _type = other._type;
         _size = other._size;
@@ -66,15 +66,15 @@ public:
     }
 
     ~Payload() { unset(); }
-    void unset() { memset( _buffer, 0, PAYLOAD_MAX_SIZE_BUFFER); _type = PAYLOAD_TYPE::UNSET; _size = 0;}
+    void unset() { memset( _buffer, 0, PAYLOAD_MAX_SIZE_BUFFER); _type = PRIORITY::UNSET_ADDR; _size = 0;}
 
-    void setType(PAYLOAD_TYPE type) { _type = type; }
-    PAYLOAD_TYPE getType() {return _type; }
+    void setType(PRIORITY type) { _type = type; }
+    PRIORITY getType() {return _type; }
 
     void setSize(uint8_t size) { _size = size; }
     uint8_t getSize() { return _size; }
 
-    bool setPayload(PAYLOAD_TYPE type, void* information, uint8_t size) {
+    bool setPayload(PRIORITY type, void* information, uint8_t size) {
         if (information == nullptr) {
             return false;
         }
@@ -87,12 +87,12 @@ public:
     }
 
     bool getPayload(void* information) {
-        PAYLOAD_TYPE type;
+        PRIORITY type;
         uint8_t size;
         return getPayload(information, type, size);
     }
 
-    bool getPayload(void* information, PAYLOAD_TYPE &type, uint8_t &size) {
+    bool getPayload(void* information, PRIORITY &type, uint8_t &size) {
         if (information == nullptr) {
             return false;
         }
@@ -108,7 +108,7 @@ public:
     void *getInformation() { return reinterpret_cast<void*>(_buffer); }
 
 private:
-    PAYLOAD_TYPE _type;
+    PRIORITY _type;
     uint8_t      _buffer[PAYLOAD_MAX_SIZE_BUFFER];
     uint8_t      _size;
 };
diff --git a/arduino/common/lib/CommsControl/CommsControl.cpp b/arduino/common/lib/CommsControl/CommsControl.cpp
index d9e1ec68d623df8e42d403320c77b3df2532be68..e8f601da3c1bf219338b2df719d98e9cdc301603 100644
--- a/arduino/common/lib/CommsControl/CommsControl.cpp
+++ b/arduino/common/lib/CommsControl/CommsControl.cpp
@@ -88,7 +88,7 @@ void CommsControl::receiver() {
                             uint8_t address = *_comms_tmp.getAddress();
 
                             // to decide what kind of packets received
-                            PAYLOAD_TYPE type = getInfoType(address);
+                            PRIORITY type = getInfoType(address);
 
                             // switch on received data to know what to do - received ACK/NACK or other
                             switch(control & COMMS_CONTROL_TYPES) {
@@ -136,8 +136,8 @@ void CommsControl::receiver() {
 }
 
 bool CommsControl::writePayload(Payload &pl) {
-    PAYLOAD_TYPE payload_type = pl.getType();
-    if (payload_type != PAYLOAD_TYPE::UNSET) {
+    PRIORITY payload_type = pl.getType();
+    if (payload_type != PRIORITY::UNSET_ADDR) {
         // create comms format using payload, the type is deduced from the payload itself
         CommsFormat comms = CommsFormat(pl);
 
@@ -243,7 +243,7 @@ void CommsControl::resendPacket(RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING>
 
 
 // receiving anything of commsFormat
-bool CommsControl::receivePacket(PAYLOAD_TYPE &type) {
+bool CommsControl::receivePacket(PRIORITY &type) {
     _payload_tmp.unset();
     _payload_tmp.setPayload(type, reinterpret_cast<void *>(_comms_tmp.getInformation()), _comms_tmp.getInfoSize());
 
@@ -258,7 +258,7 @@ bool CommsControl::receivePacket(PAYLOAD_TYPE &type) {
 }
 
 // if FCS is ok, remove from queue
-void CommsControl::finishPacket(PAYLOAD_TYPE &type) {
+void CommsControl::finishPacket(PRIORITY &type) {
     RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING> *queue = getQueue(type);
 
     if (queue != nullptr && !queue->isEmpty()) {
@@ -274,27 +274,19 @@ void CommsControl::finishPacket(PAYLOAD_TYPE &type) {
     }
 }
 
-PAYLOAD_TYPE CommsControl::getInfoType(uint8_t &address) {
-    switch (address & PACKET_TYPE) {
-        case PACKET_ALARM:
-            return PAYLOAD_TYPE::ALARM;
-        case PACKET_CMD:
-            return PAYLOAD_TYPE::CMD;
-        case PACKET_DATA:
-            return PAYLOAD_TYPE::DATA;
-        default:
-            return PAYLOAD_TYPE::UNSET;
-    }
+PRIORITY CommsControl::getInfoType(uint8_t &address) {
+    // return enum element corresponding to the address
+    return (PRIORITY)(address & PACKET_TYPE);
 }
 
 // get link to queue according to packet format
-RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING> *CommsControl::getQueue(PAYLOAD_TYPE &type) {
+RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING> *CommsControl::getQueue(PRIORITY &type) {
     switch (type) {
-        case PAYLOAD_TYPE::ALARM:
+        case PRIORITY::ALARM_ADDR:
             return &_ring_buff_alarm;
-        case PAYLOAD_TYPE::CMD:
+        case PRIORITY::CMD_ADDR:
             return &_ring_buff_cmd;
-        case PAYLOAD_TYPE::DATA:
+        case PRIORITY::DATA_ADDR:
             return &_ring_buff_data;
         default:
             return nullptr;
diff --git a/arduino/common/lib/CommsControl/CommsControl.h b/arduino/common/lib/CommsControl/CommsControl.h
index 13ebe690a37f6a19ef86b5a144f4d6ebece2973c..b623ebb2d0f9b47083ad895e7f312e616f6a3b45 100644
--- a/arduino/common/lib/CommsControl/CommsControl.h
+++ b/arduino/common/lib/CommsControl/CommsControl.h
@@ -26,13 +26,13 @@ public:
     void receiver();
 
 private:
-    RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING> *getQueue(PAYLOAD_TYPE &type);
-    PAYLOAD_TYPE getInfoType(uint8_t &address);
+    RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING> *getQueue(PRIORITY &type);
+    PRIORITY getInfoType(uint8_t &address);
 
     void sendQueue    (RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING> *queue);
     void resendPacket (RingBuf<CommsFormat, COMMS_MAX_SIZE_RB_SENDING> *queue);
-    bool receivePacket(PAYLOAD_TYPE &type);
-    void finishPacket (PAYLOAD_TYPE &type);
+    bool receivePacket(PRIORITY &type);
+    void finishPacket (PRIORITY &type);
 
     bool encoder(uint8_t* payload, uint8_t data_size);
     bool decoder(uint8_t* payload, uint8_t dataStart, uint8_t data_stop);
diff --git a/arduino/common/lib/CommsControl/CommsFormat.cpp b/arduino/common/lib/CommsControl/CommsFormat.cpp
index 1d034227560e3c1e61f7505273e3b0897b581c60..01c4c882070e0e7b245f50eb726b93e01a4151d6 100644
--- a/arduino/common/lib/CommsControl/CommsFormat.cpp
+++ b/arduino/common/lib/CommsControl/CommsFormat.cpp
@@ -8,13 +8,13 @@ CommsFormat::CommsFormat(uint8_t info_size, uint8_t address, uint16_t control) {
 CommsFormat::CommsFormat(Payload &pl) {
     uint8_t address;
     switch (pl.getType()) {
-        case PAYLOAD_TYPE::ALARM:
+        case PRIORITY::ALARM_ADDR:
             address = PACKET_ALARM;
             break;
-        case PAYLOAD_TYPE::CMD:
+        case PRIORITY::CMD_ADDR:
             address = PACKET_CMD;
             break;
-        case PAYLOAD_TYPE::DATA:
+        case PRIORITY::DATA_ADDR:
             address = PACKET_DATA;
             break;
         default:
diff --git a/arduino/hev_prototype_v1/src/UILoop.cpp b/arduino/hev_prototype_v1/src/UILoop.cpp
index e841b3e7590feddb98cf210eb4e5895da0c002c3..61787fdaab7a576987dcca2b3a3bbdd661d5c350 100644
--- a/arduino/hev_prototype_v1/src/UILoop.cpp
+++ b/arduino/hev_prototype_v1/src/UILoop.cpp
@@ -25,7 +25,7 @@ void UILoop::receiveCommands()
     // check any received payload
     if(_comms->readPayload(_plReceive)) {
 
-      if (_plReceive.getType() == PAYLOAD_TYPE::CMD) {
+      if (_plReceive.getType() == PRIORITY::CMD_ADDR) {
           // apply received cmd to ui loop
           cmd_format cmd;
           _plReceive.getPayload(reinterpret_cast<void*>(&cmd));
@@ -33,7 +33,7 @@ void UILoop::receiveCommands()
       }
 
       // unset received type not to read it again
-      _plReceive.setType(PAYLOAD_TYPE::UNSET);
+      _plReceive.setType(PRIORITY::UNSET_ADDR);
     }
 }
 
@@ -60,7 +60,7 @@ void UILoop::reportFastReadings()
         _fast_data.pressure_o2_regulated  = readings.pressure_o2_regulated;
         _fast_data.pressure_diff_patient  = readings.pressure_diff_patient;
 
-        _plSend.setPayload(PAYLOAD_TYPE::DATA, reinterpret_cast<void *>(&_fast_data), sizeof(_fast_data));
+        _plSend.setPayload(PRIORITY::DATA_ADDR, reinterpret_cast<void *>(&_fast_data), sizeof(_fast_data));
         _comms->writePayload(_plSend);
         _fast_report_time = tnow;
     }
@@ -111,7 +111,7 @@ void UILoop::reportReadbackValues()
         // _readback_data.peep = _breathing_loop->peep();
         _readback_data.inhale_exhale_ratio = _breathing_loop->getIERatio();
 
-        _plSend.setPayload(PAYLOAD_TYPE::DATA, reinterpret_cast<void *>(&_readback_data), sizeof(_readback_data));
+        _plSend.setPayload(PRIORITY::DATA_ADDR, reinterpret_cast<void *>(&_readback_data), sizeof(_readback_data));
         _comms->writePayload(_plSend);
         _readback_report_time = tnow;
     }
@@ -125,7 +125,7 @@ void UILoop::reportCycleReadings()
 
         _cycle_data.timestamp =  tnow;
 
-        _plSend.setPayload(PAYLOAD_TYPE::DATA, reinterpret_cast<void *>(&_cycle_data), sizeof(_cycle_data));
+        _plSend.setPayload(PRIORITY::DATA_ADDR, reinterpret_cast<void *>(&_cycle_data), sizeof(_cycle_data));
         _comms->writePayload(_plSend);
         _cycle_report_time = tnow;
     }
diff --git a/arduino/hev_prototype_v1/src/common.h b/arduino/hev_prototype_v1/src/common.h
index 5a0501c858987cce51d03691d1976fc4e13ce8b0..a54938eff5ab88f859ac3c8cb5cd961cfb0bfd55 100644
--- a/arduino/hev_prototype_v1/src/common.h
+++ b/arduino/hev_prototype_v1/src/common.h
@@ -23,6 +23,16 @@
 // 
 const float MAX_VALVE_FRAC_OPEN = 0.68;
 // input params
+enum PAYLOAD_TYPE : uint8_t {
+    UNSET        = 0,
+    DATA         = 1,
+    READBACK     = 2,
+    CYCLE        = 3,
+    THRESHOLDS   = 4,
+    CMD          = 5,
+    ALARM        = 6
+};
+
 enum CMD_TYPE  : uint8_t {
     GENERAL           =  1,
     SET_DURATION      =  2,
@@ -65,11 +75,12 @@ enum VENTILATION_MODE : uint8_t {
 
 #pragma pack(1)
 struct cmd_format {
-    uint8_t  version   = HEV_FORMAT_VERSION;
-    uint32_t timestamp = 0;
-    uint8_t  cmd_type  = 0;
-    uint8_t  cmd_code  = 0;
-    uint32_t param     = 0;
+    uint8_t  version      = HEV_FORMAT_VERSION;
+    uint32_t timestamp    = 0;
+    uint8_t  payload_type = PAYLOAD_TYPE::CMD;
+    uint8_t  cmd_type     = 0;
+    uint8_t  cmd_code     = 0;
+    uint32_t param        = 0;
 };
 #pragma pack()
 
@@ -110,28 +121,22 @@ enum ALARM_CODES: uint8_t {
 
 #pragma pack(1)
 struct alarm_format {
-    uint8_t  version    = HEV_FORMAT_VERSION;
-    uint32_t timestamp  = 0;
-    uint8_t  alarm_type = 0;
-    uint8_t  alarm_code = 0;
-    uint32_t param      = 0;
+    uint8_t  version      = HEV_FORMAT_VERSION;
+    uint32_t timestamp    = 0;
+    uint8_t  payload_type = PAYLOAD_TYPE::ALARM;
+    uint8_t  alarm_type   = 0;
+    uint8_t  alarm_code   = 0;
+    uint32_t param        = 0;
 };
 #pragma pack()
 
-enum DATA_TYPE: uint8_t  {
-    FAST       =  1,
-    READBACK   =  2,
-    CYCLE      =  3,
-    THRESHOLDS =  4
-};
-
 // struct for all data sent
 #pragma pack(1)
 struct fast_data_format {
 // fast values - read every ~10 ms
     uint8_t  version                = HEV_FORMAT_VERSION;
     uint32_t timestamp              = 0;
-    uint8_t  data_type              = DATA_TYPE::FAST;
+    uint8_t  payload_type           = PAYLOAD_TYPE::DATA;
     uint8_t  fsm_state              = 0; //UNKNOWN
     uint16_t pressure_air_supply    = 0;
     float    pressure_air_regulated = 0;
@@ -144,9 +149,9 @@ struct fast_data_format {
     float    pressure_diff_patient  = 0;
     uint16_t ambient_pressure       = 0;
     uint16_t ambient_temperature    = 0;
-    float airway_pressure           = 0;
-    float flow                      = 0;
-    float volume                    = 0;//41
+    float airway_pressure           = 0.0;
+    float flow                      = 0.0;
+    float volume                    = 0.0;
 };
 #pragma pack()
 
@@ -155,7 +160,7 @@ struct readback_data_format {
 // readback values
     uint8_t  version                  = HEV_FORMAT_VERSION;
     uint32_t timestamp                = 0;
-    uint8_t  data_type                = DATA_TYPE::READBACK;
+    uint8_t  payload_type             = PAYLOAD_TYPE::READBACK;
     uint16_t duration_calibration     = 0;
     uint16_t duration_buff_purge      = 0;
     uint16_t duration_buff_flush      = 0;
@@ -192,20 +197,21 @@ struct cycle_data_format {
 // per breath values
     uint8_t  version                    = HEV_FORMAT_VERSION;
     uint32_t timestamp                  = 0;
-    uint8_t  data_type                  = DATA_TYPE::CYCLE;
+    uint8_t  payload_type               = PAYLOAD_TYPE::CYCLE;
+
 
-    float respiratory_rate              = 0;
+    float respiratory_rate              = 0.0;
 
-    float tidal_volume                  = 0;
-    float exhaled_tidal_volume          = 0;
-    float inhaled_tidal_volume          = 0;
+    float tidal_volume                  = 0.0;
+    float exhaled_tidal_volume          = 0.0;
+    float inhaled_tidal_volume          = 0.0;
 
-    float minute_volume                 = 0;
-    float exhaled_minute_volume         = 0;
-    float inhaled_minute_volume         = 0;
+    float minute_volume                 = 0.0;
+    float exhaled_minute_volume         = 0.0;
+    float inhaled_minute_volume         = 0.0;
 
-    float lung_compliance               = 0;
-    float static_compliance             = 0;
+    float lung_compliance               = 0.0;
+    float static_compliance             = 0.0;
 
     uint16_t inhalation_pressure        = 0;  // mean airway pressure
     uint16_t peak_inspiratory_pressure  = 0;  
diff --git a/raspberry-dataserver/CommsCommon.py b/raspberry-dataserver/CommsCommon.py
index 2e1fbe049b4106c9b39768d73805b55b52f78ab4..fc1c9f7dd9bbb6d6f19521ca5accc06a7ec9ba27 100644
--- a/raspberry-dataserver/CommsCommon.py
+++ b/raspberry-dataserver/CommsCommon.py
@@ -8,7 +8,7 @@ logging.basicConfig(level=logging.INFO,
                     format='%(asctime)s - %(levelname)s - %(message)s')
 
 # VERSIONING
-# change version in BaseFormat for all data
+# change version in PayloadFormat for all data
 # i.e. if and of DataFormat, CommandFormat or AlarmFormat change
 # then change the RPI_VERSION
 
@@ -115,46 +115,67 @@ class BL_STATES(Enum):
 
 @unique
 class PAYLOAD_TYPE(IntEnum):
+    UNSET      = 0
     DATA       = 1
     READBACK   = 2
     CYCLE      = 3
     THRESHOLDS = 4
     CMD        = 5
     ALARM      = 6
-    UNSET      = 7
 
 @dataclass
-class BaseFormat():
+class PayloadFormat():
     # class variables excluded from init args and output dict
-    _RPI_VERSION: ClassVar[int] = field(default=0xA3, 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)
-
-    # class variables
-    version:   int = 0
-    timestamp: int = 0
+    _RPI_VERSION: ClassVar[int]       = field(default=0xA3, init=False, repr=False)
+    _dataStruct:  ClassVar[Any]       = field(default=Struct("<BIB"), init=False, repr=False)
+    _byteArray:   ClassVar[bytearray] = field(default=None, init=False, repr=False)
+
+    # Meta information
+    version: int               = 0
+    timestamp: int             = 0
+    payload_type: PAYLOAD_TYPE = PAYLOAD_TYPE.UNSET
+
+    @classmethod
+    def fromByteArray(cls, rec_bytes):
+        """Automatically determine which subclass to initialise as"""
+        DATA_TYPE_TO_CLASS = {
+            1: DataFormat,
+            2: ReadbackFormat,
+            3: CycleFormat,
+            #4: ThresholdFormat,
+            5: CommandFormat,
+            6: AlarmFormat,
+        }
+        ReturnType = DATA_TYPE_TO_CLASS[rec_bytes[5]]
+        data = ReturnType._dataStruct.unpack(rec_bytes)
+        return ReturnType(*data)
 
     @property
     def byteArray(self) -> bytearray:
-        return self._dataStruct.pack(*[
+        self.toByteArray()
+        return self._byteArray
+
+    @byteArray.setter
+    def byteArray(self, byte_array) -> None:
+        self._byteArray = byte_array
+
+    def toByteArray(self) -> None:
+        self.version = self._RPI_VERSION
+        self._byteArray = self._dataStruct.pack(*[
             v.value if isinstance(v, IntEnum) or isinstance(v, Enum) else v
             for v in asdict(self).values()
         ])
 
-    # at the minute not generalised. needs to be overridden
-    def fromByteArray(self, byteArray: bytearray) -> None:
-        (self.version, self.timestamp) = self._dataStruct.unpack(byteArray)
-
     # check for mismatch between pi and microcontroller version
     def checkVersion(self):
         if self._RPI_VERSION != self.version : 
             raise Exception('Version Mismatch', "PI:", self._RPI_VERSION, "uC:", self.version)
 
     def getSize(self) -> int:
-        return self._dataStruct.size
+        return len(self.byteArray)
 
     def getType(self) -> Any:
-        return self._type
+        return self.payload_type
     
     def getDict(self) -> Dict:
         return {k: v.name if isinstance(v, IntEnum) or isinstance(v, Enum) else v for k, v in asdict(self).items()}
@@ -164,14 +185,13 @@ class BaseFormat():
 # fast data payload
 # =======================================
 @dataclass
-class DataFormat(BaseFormat):
+class DataFormat(PayloadFormat):
     # subclass dataformat
     _dataStruct = Struct("<BIBBHfHffffHfHHfff")
-    _type = PAYLOAD_TYPE.DATA
+    payload_type: PAYLOAD_TYPE = PAYLOAD_TYPE.DATA
 
     # subclass member variables
-    data_type: int                = 1
-    fsm_state: BL_STATES          = BL_STATES.IDLE
+    fsm_state: BL_STATES        = BL_STATES.IDLE
     pressure_air_supply: int      = 0
     pressure_air_regulated: float = 0.0
     pressure_o2_supply: int       = 0
@@ -196,7 +216,7 @@ class DataFormat(BaseFormat):
         tmp_state = 0
         (self.version,
         self.timestamp,
-        _, # dummy to skip datatype
+        self.payload_type,
         tmp_state,
         self.pressure_air_supply,
         self.pressure_air_regulated,
@@ -211,14 +231,9 @@ class DataFormat(BaseFormat):
         self.ambient_temperature,
         self.airway_pressure,
         self.flow,
-        self.volume
-        ) = self._dataStruct.unpack(byteArray) 
-        try:
-            self.fsm_state = BL_STATES(tmp_state)
-            self.checkVersion()
-        except Exception:
-            # no longer silently die, catch Exceptions higher up
-            raise
+        self.volume) = self._dataStruct.unpack(byteArray) 
+        self.fsm_state = BL_STATES(tmp_state)
+        self.checkVersion()
         self._byteArray = byteArray
 
 
@@ -226,11 +241,10 @@ class DataFormat(BaseFormat):
 # readback data payload
 # =======================================
 @dataclass
-class ReadbackFormat(BaseFormat):
+class ReadbackFormat(PayloadFormat):
     _dataStruct = Struct("<BIBHHHHHHHHHHHffBBBBBBBBBBBBf")
-    _type = PAYLOAD_TYPE.READBACK
+    payload_type: PAYLOAD_TYPE = PAYLOAD_TYPE.READBACK
 
-    data_type: int                = 2
     duration_calibration: int     = 0
     duration_buff_purge: int      = 0
     duration_buff_flush: int      = 0
@@ -268,7 +282,7 @@ class ReadbackFormat(BaseFormat):
         tmp_mode = 0
         (self.version,
         self.timestamp,
-        _, # dummy to skip datatype
+        self.payload_type,
         self.duration_calibration,
         self.duration_buff_purge,
         self.duration_buff_flush,
@@ -308,12 +322,11 @@ class ReadbackFormat(BaseFormat):
 # cycle data payload
 # =======================================
 @dataclass
-class CycleFormat(BaseFormat):
+class CycleFormat(PayloadFormat):
     # subclass dataformat
     _dataStruct = Struct("<BIBfffffffffHHHHBHHB")
-    _type = PAYLOAD_TYPE.CYCLE
+    payload_type: PAYLOAD_TYPE = PAYLOAD_TYPE.CYCLE
 
-    data_type: int                 = 3
     respiratory_rate: float        = 0.0
     tidal_volume: float            = 0.0
     exhaled_tidal_volume: float    = 0.0
@@ -339,7 +352,7 @@ class CycleFormat(BaseFormat):
         #logging.info(binascii.hexlify(byteArray))
         (self.version,
         self.timestamp,
-        _, # dummy to skip datatype
+        self.payload_type,
         self.respiratory_rate,
         self.tidal_volume,
         self.exhaled_tidal_volume,
@@ -371,9 +384,9 @@ class CycleFormat(BaseFormat):
 # cmd type payload
 # =======================================
 @dataclass
-class CommandFormat(BaseFormat):
-    _dataStruct = Struct("<BIBBI")
-    _type = PAYLOAD_TYPE.CMD
+class CommandFormat(PayloadFormat):
+    _dataStruct = Struct("<BIBBBI")
+    payload_type: PAYLOAD_TYPE = PAYLOAD_TYPE.CMD
 
     cmd_type: int = 0
     cmd_code: int = 0
@@ -382,6 +395,7 @@ class CommandFormat(BaseFormat):
     def fromByteArray(self, byteArray):
         (self.version,
         self.timestamp,
+        self.payload_type,
         self.cmd_type,
         self.cmd_code,
         self.param) = self._dataStruct.unpack(byteArray) 
@@ -392,18 +406,19 @@ class CommandFormat(BaseFormat):
 # alarm type payload
 # =======================================
 @dataclass
-class AlarmFormat(BaseFormat):
-    _dataStruct = Struct("<BIBBI")
-    _type = PAYLOAD_TYPE.ALARM
+class AlarmFormat(PayloadFormat):
+    _dataStruct = Struct("<BIBBBI")
+    payload_type: PAYLOAD_TYPE = PAYLOAD_TYPE.ALARM
 
     alarm_type: int = 0
     alarm_code: ALARM_CODES = ALARM_CODES.UNKNOWN
-    param: int      = 0
+    param: int = 0
 
     def fromByteArray(self, byteArray):
         alarm = 0
         (self.version,
         self.timestamp,
+        self.payload_type,
         self.alarm_type,
         alarm,
         self.param) = self._dataStruct.unpack(byteArray)
diff --git a/raspberry-dataserver/CommsDebug.py b/raspberry-dataserver/CommsDebug.py
index 73cc2c2a7b2adb097d5e103b38680e60851c3c3e..41ede571c56e0a2317a0cc60f9a9b204c2c667fa 100755
--- a/raspberry-dataserver/CommsDebug.py
+++ b/raspberry-dataserver/CommsDebug.py
@@ -1,25 +1,27 @@
 #!/usr/bin/env python3
-from CommsControl import CommsControl
+from CommsLLI import CommsLLI
 from CommsCommon import *
+import asyncio
 import logging
 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 from serial.tools import list_ports
 import sys
 import time
 
-port_device = "" 
-for port in list_ports.comports():
-    vidpid = ""
-    if port.pid != None and port.vid != None:
-        vidpid = f"{ port.vid:04x}:{port.pid:04x}".upper()
-        print(vidpid)
-    if port.manufacturer and "ARDUINO" in port.manufacturer.upper():
-        port_device = port.device 
-    elif vidpid == "10C4:EA60" :
-        port_device = port.device 
-    elif len(sys.argv) > 1:
-        port_device = sys.argv[1]
-comms = CommsControl(port = port_device)
+def getTTYPort():
+    port_device = "" 
+    for port in list_ports.comports():
+        vidpid = ""
+        if port.pid != None and port.vid != None:
+            vidpid = f"{ port.vid:04x}:{port.pid:04x}".upper()
+            print(vidpid)
+        if port.manufacturer and "ARDUINO" in port.manufacturer.upper():
+            port_device = port.device 
+        elif vidpid == "10C4:EA60" :
+            port_device = port.device 
+        elif len(sys.argv) > 1:
+            port_device = sys.argv[1]
+    return port_device
 
 
 class Dependant(object):
@@ -33,32 +35,49 @@ class Dependant(object):
         if hasattr(payload, 'ventilation_mode'):
             logging.info(f"payload received: {payload.ventilation_mode}")
         #logging.info(f"payload received: {payload.fsm_state}")
+        if payload.getType() == 1:
+            logging.info(f"Fsm state: {payload.fsm_state}")
         #logging.info(f"payload received: {payload.timestamp}")
-        if hasattr(payload, 'fsm_state'):
-            logging.info(f"payload received: {payload.fsm_state}")
         if hasattr(payload, 'duration_inhale'):
             logging.info(f"payload received: inhale duration = {payload.duration_inhale} ")
         self._llipacket = payload.getDict() # returns a dict
-        # pop from queue - protects against Dependant going down and not receiving packets
-        self._lli.pop_payloadrecv()
 
-dep = Dependant(comms)
 
 # initialise as start command, automatically executes toByteArray()
-#cmd = CommandFormat(cmd_type=CMD_TYPE.GENERAL.value, cmd_code=CMD_GENERAL.START.value, param=0)
-#cmd = CommandFormat(cmd_type=CMD_TYPE.SET_TIMEOUT.value, cmd_code=CMD_SET_TIMEOUT.INHALE.value, param=1111)
-time.sleep(4)
-cmd = CommandFormat(cmd_type=CMD_TYPE.GENERAL.value, cmd_code=CMD_GENERAL.START.value, param=0)
-comms.writePayload(cmd)
-print('sent cmd start')
-while True:
-    time.sleep(15)
-    cmd = CommandFormat(cmd_type=CMD_TYPE.SET_MODE.value, cmd_code=VENTILATION_MODE.LAB_MODE_PURGE.value, param=0)
+async def commsDebug():
+    #cmd = CommandFormat(cmd_type=CMD_TYPE.GENERAL.value, cmd_code=CMD_GENERAL.START.value, param=0)
+    #cmd = CommandFormat(cmd_type=CMD_TYPE.SET_TIMEOUT.value, cmd_code=CMD_SET_TIMEOUT.INHALE.value, param=1111)
+    cmd = CommandFormat(cmd_type=CMD_TYPE.GENERAL.value, cmd_code=CMD_GENERAL.START.value, param=0)
+    await asyncio.sleep(4)
     comms.writePayload(cmd)
-    print('sent cmd purge')
-    time.sleep(15)
-    cmd = CommandFormat(cmd_type=CMD_TYPE.GENERAL.value, cmd_code=CMD_GENERAL.STOP.value, param=0)
-    comms.writePayload(cmd)
-    print('sent cmd stop')
-    pass
+    print('sent cmd start')
+    while True:
+        await asyncio.sleep(15)
+        cmd = CommandFormat(cmd_type=CMD_TYPE.SET_MODE.value, cmd_code=VENTILATION_MODE.LAB_MODE_PURGE.value, param=0)
+        comms.writePayload(cmd)
+        print('sent cmd purge')
+        await asyncio.sleep(15)
+        cmd = CommandFormat(cmd_type=CMD_TYPE.GENERAL.value, cmd_code=CMD_GENERAL.STOP.value, param=0)
+        comms.writePayload(cmd)
+        print('sent cmd stop')
+
+try:
+    # setup serial device and init server
+    loop = asyncio.get_event_loop()
+    comms = CommsLLI(loop)
+    dep = Dependant(comms)
 
+    # create tasks
+    lli = comms.main(getTTYPort(), 115200)
+    debug = commsDebug()
+    tasks = [lli, debug]
+
+    # run tasks
+    asyncio.gather(*tasks, return_exceptions=True)
+    loop.run_forever()
+except asyncio.CancelledError:
+    pass
+except KeyboardInterrupt:
+    logging.info("Closing LLI")
+finally:
+    loop.close()
diff --git a/raspberry-dataserver/CommsFormat.py b/raspberry-dataserver/CommsFormat.py
index 5e3b4e02676b1500d4b08141b42d585bad8dcb68..56b9d726d803217d043b8097aaa1cec1c3b8dce5 100644
--- a/raspberry-dataserver/CommsFormat.py
+++ b/raspberry-dataserver/CommsFormat.py
@@ -2,45 +2,61 @@
 
 # Communication protocol based on HDLC format
 # author Peter Svihra <peter.svihra@cern.ch>
+# adapted for async DM
 
 import libscrc
+import binascii
+import logging
+from typing import ClassVar
+
+# Set up logging
+logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s')
+logging.getLogger().setLevel(logging.DEBUG)
+
 
 def commsFromBytes(byteArray):
     comms = CommsFormat()
     comms.copyBytes(byteArray)
-    
     return comms
 
 def generateAlarm(payload):
     comms = CommsFormat(info_size = payload.getSize(), address = 0xC0)
-    comms.setInformation(payload.byteArray)
+    bytelist = bytes([byte for byte in bytearray(payload.byteArray)])
+    comms.setInformation(bytelist)
     return comms
 
 def generateCmd(payload):
     comms = CommsFormat(info_size = payload.getSize(), address = 0x80)
-    comms.setInformation(payload.byteArray)
+    bytelist = bytes([byte for byte in bytearray(payload.byteArray)])
+    comms.setInformation(bytelist)
     return comms
 
 def generateData(payload):
     comms = CommsFormat(info_size = payload.getSize(), address = 0x40)
-    comms.setInformation(payload.byteArray)
+    bytelist = bytes([byte for byte in bytearray(payload.byteArray)])
+    comms.setInformation(bytelist)
     return comms
 
+class CommsChecksumError(Exception):
+    pass
 
 # basic format based on HDLC
 class CommsFormat:
-    def __init__(self, info_size = 0, address = 0x00, control = [0x00, 0x00]):
+    start_flag: ClassVar[int] = bytes([0x7E])
+    escape_flag: ClassVar[int] = bytes([0x7D])
+
+    def __init__(self, info_size=0, address=0x00, control=[0x00, 0x00]):
         self._data = bytearray(7 + info_size)
         self._info_size = info_size
         self._crc = None
-        
-        self.assignBytes(self.getStart()  , bytes([0x7E])   , calc_crc = False)
+
+        self.assignBytes(self.getStart()  , self.start_flag  , calc_crc = False)
         self.assignBytes(self.getAddress(), bytes([address]), calc_crc = False)
         self.assignBytes(self.getControl(), bytes(control)  , calc_crc = False)
-        self.assignBytes(self.getStop()   , bytes([0x7E])   , calc_crc = False)
-        
+        self.assignBytes(self.getStop()   , self.start_flag  , calc_crc = False)
+ 
         self.generateCrc()
-        
+ 
     def getStart(self):
         return 0
     def getAddress(self):
@@ -53,17 +69,17 @@ class CommsFormat:
         return 4 + self._info_size
     def getStop(self):
         return 4 + self._info_size + 2
-    
+ 
     def setAddress(self, address):
         self.assignBytes(self.getAddress(), bytes([address]), 1)
-        
+ 
     def setControl(self, control):
         self.assignBytes(self.getControl(), bytes(control), 2)
-    
+ 
     def setInformation(self, bytes_array):
         # convert provided value
         self.assignBytes(self.getInformation(), bytes_array)
-        
+ 
     def setSequenceSend(self, value):
         # sequence sent valid only for info frames (not supervisory ACK/NACK)
         if (self._data[self.getControl() + 1] & 0x01) == 0:
@@ -73,51 +89,129 @@ class CommsFormat:
     def setSequenceReceive(self, value):
         value = (value << 1) & 0xFE
         self.assignBytes(self.getControl()    , value.to_bytes(1, byteorder='little'), 1)
-        
+ 
     def getSequenceSend(self):
         # sequence sent valid only for info frames (not supervisory ACK/NACK)
         if (self._data[self.getControl() + 1] & 0x01) == 0:
             return (self._data[self.getControl() + 1] >> 1) & 0x7F
         else:
             return 0xFF
-        
+ 
     def getSequenceReceive(self):
         return (self._data[self.getControl()] >> 1) & 0x7F
-        
+ 
     def assignBytes(self, start, values, calc_crc = True):
         for idx in range(len(values)):
             self._data[start + idx] = values[idx]
         if calc_crc:
             self.generateCrc()
-        
+ 
     # generate checksum
     def generateCrc(self, assign = True):
         self._crc = libscrc.x25(bytes(self._data[self.getAddress():self.getFcs()])).to_bytes(2, byteorder='little')
         if assign:
             self.assignBytes(self.getFcs(), self._crc, calc_crc = False)
-            
+ 
     def compareCrc(self):
         self.generateCrc(False)
         fcs = self.getData()[self.getFcs():self.getFcs()+2]
         return self._crc in fcs
-    
-    
+ 
     def getData(self):
         return self._data
 
     def copyData(self, data_array):
         self.copyBytes(data_array.to_bytes(self._info_size, byteorder='little'))
-        
+ 
     def copyBytes(self, bytes_array):
         self._info_size = len(bytes_array) - 7
         self._data     = bytes_array
 
+    # escape any 0x7D or 0x7E with 0x7D and swap bit 5
+    def encode(self):
+        data = self._data
+        stream = [[int.from_bytes(self.escape_flag, 'little'), byte ^ (1<<5)] if bytes([byte]) == self.escape_flag or bytes([byte]) == self.start_flag else [byte] for byte in data[1:-1]]
+        stream = [byte for byteList in stream for byte in byteList]
+        result = bytes([data[0]]) + bytes(stream) + bytes([data[-1]])
+        return result
+
+
+class CommsPacket(object):
+    start_flag: ClassVar[int] = bytes([0x7E])
+    escape_flag: ClassVar[int] = bytes([0x7D])
+
+    def __init__(self, rawdata):
+        self._data = rawdata
+        self._sequence_receive = 0
+        self._address = None
+        self._byteArray = None
+        self._datavalid = False
+        self._acked = None
+
+    @property
+    def byteArray(self):
+        return self._byteArray
+
+    @property
+    def sequence_receive(self):
+        return self._sequence_receive
+
+    @property
+    def address(self):
+        return self._address
+
+    @property
+    def acked(self):
+        return self._acked
+
+    def undoEscape(self):
+        data = self._data
+
+        packets = data[1:-1]
+
+        # remove escape sequences for bytes containing escape patterns and convert back
+        indRemove = [idx for idx in range(len(packets)) if bytes([packets[idx]]) == self.escape_flag]
+        indChange = [idx+1 for idx in indRemove]
+
+        stream = [packets[idx] ^ (1<<5) if idx in indChange else packets[idx] for idx in range(len(packets)) if idx not in indRemove]
+        result = bytes([data[0]]) + bytes(stream) + bytes([data[-1]])
+        return result
+
+    def decode(self):
+        """Returns payload bytearray or None for ack/nack"""
+        byteArray = self.undoEscape()
+    
+        # check checksum and control bytes
+        tmp_comms = CommsFormat()
+        tmp_comms.copyBytes(byteArray)
+        if tmp_comms.compareCrc():
+            control     = tmp_comms.getData()[tmp_comms.getControl()+1]
+            self._sequence_receive = (tmp_comms.getData()[tmp_comms.getControl()] >> 1) & 0x7F
+            self._address = tmp_comms.getData()[1]
+            
+            # get type of packet
+            ctrl_flag    = control & 0x0F
+            if ctrl_flag == 0x05:
+                # received NACK
+                self._acked = False
+            elif ctrl_flag == 0x01:
+                # received ACK
+                self._acked = True
+            else:
+                # received data
+                self._sequence_receive = ((control >> 1) & 0x7F) + 1
+                self._byteArray = tmp_comms.getData()[tmp_comms.getInformation():tmp_comms.getFcs()]
+        else:
+            raise CommsChecksumError
+
+        return self._byteArray
+
 # ACK specific formating
 class CommsACK(CommsFormat):
     def __init__(self, address):
-        super().__init__(control = [0x00, 0x01], address = address)
-        
+        super().__init__(control = [0x00, 0x01], address=address)
+ 
 # NACK specific formating
 class CommsNACK(CommsFormat):
     def __init__(self, address):
-        super().__init__(control = [0x00, 0x05], address = address)
+        super().__init__(control = [0x00, 0x05], address=address)
diff --git a/raspberry-dataserver/CommsLLI.py b/raspberry-dataserver/CommsLLI.py
new file mode 100755
index 0000000000000000000000000000000000000000..b62ecfc9cbd9021572f1837a62c63493714b0546
--- /dev/null
+++ b/raspberry-dataserver/CommsLLI.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env python3
+
+import asyncio
+import serial_asyncio
+import logging
+import binascii
+import time
+from collections import deque
+from struct import error as StructError
+from CommsCommon import PayloadFormat, PAYLOAD_TYPE
+from CommsFormat import CommsPacket, CommsACK, CommsNACK, CommsChecksumError, generateAlarm, generateCmd, generateData
+
+# Set up logging
+logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s')
+logging.getLogger().setLevel(logging.INFO)
+
+class CommsLLI:
+    def __init__(self, loop):
+        super().__init__()
+        # IO
+        self._loop = loop
+        self._reader = None
+        self._writer = None
+        self._connected = False
+
+        # send
+        self._queue_size = 16
+        self._alarms   = asyncio.Queue(maxsize=self._queue_size, loop=self._loop)
+        self._commands = asyncio.Queue(maxsize=self._queue_size, loop=self._loop)
+        self._data     = asyncio.Queue(maxsize=self._queue_size, loop=self._loop)
+
+        # acks from arduino
+        self._dvAlarms   = asyncio.Event(loop=self._loop)
+        self._dvCommands = asyncio.Event(loop=self._loop)
+        self._dvData     = asyncio.Event(loop=self._loop)
+        # maps between address and queues/events/timeouts 
+        self._queues   = {0xC0: self._alarms, 0x80: self._commands, 0x40: self._data}
+        self._timeouts = {0xC0: 10, 0x80: 50, 0x40: 200}
+        self._acklist  = {0xC0: self._dvAlarms, 0x80: self._dvCommands, 0x40: self._dvData}
+        
+        # receive
+        #self._payloadrecv = asyncio.Queue(maxsize=self._queue_size, loop=self._loop)
+        self._payloadrecv = deque(maxlen = self._queue_size)
+        self._observers = []
+
+        # packet counting
+        self._sequence_send    = 0
+        self._sequence_receive = 0
+
+    async def main(self, device, baudrate):
+        self._reader, self._writer = await serial_asyncio.open_serial_connection(url=device, baudrate=baudrate, timeout = 2, dsrdtr = True)
+        self._connected = True
+        while self._connected:
+            #sendAlarm = self.send(0xC0)
+            sendCmd = self.send(0x80)
+            #sendData = self.send(0x40)
+            receiver = self.recv()
+            await asyncio.gather(*[receiver, sendCmd], return_exceptions=True)
+
+    async def send(self, address):
+        queue = self._queues[address]
+        while self._connected:
+            logging.debug("Waiting for Command")
+            packet = await queue.get()
+            packet.setSequenceSend(self._sequence_send)
+            for send_attempt in range(5):
+                # try to send the packet 5 times
+                try:
+                    await self.sendPacket(packet)
+                    await asyncio.wait_for(self._acklist[address].wait(), timeout=self._timeouts[address] / 1000)
+                except asyncio.TimeoutError:
+                    pass
+                except Exception:
+                    # catch everything else and propagate it
+                    raise
+                else:
+                    # acknowledged
+                    break
+
+    def writePayload(self, payload):
+        try:
+            # TODO replace these two dicts with address mappings like everywhere else
+            PAYLOAD_TYPE_TO_GEN = {
+                1: generateData,
+                2: generateData,
+                3: generateData,
+                4: generateData,
+                5: generateCmd,
+                6: generateAlarm,
+            }
+            generatePacket = PAYLOAD_TYPE_TO_GEN[payload.getType()]
+            tmp_comms = generatePacket(payload)
+
+            qlist = [v for v in self._queues.values()]
+            PAYLOAD_TYPE_TO_QUEUE = {
+                1: qlist[2],
+                2: qlist[2],
+                3: qlist[2],
+                4: qlist[2],
+                5: qlist[1],
+                6: qlist[0],
+            }
+            queue = PAYLOAD_TYPE_TO_QUEUE[payload.getType()]
+            queue.put_nowait(tmp_comms)
+
+            return True
+        except KeyError:
+            return False
+
+    async def sendPacket(self, packet):
+        if isinstance(packet, CommsACK):
+            # don't log acks
+            pass
+        elif isinstance(packet, CommsNACK):
+            logging.warning(f"Sending NACK: {binascii.hexlify(packet.encode())}")
+        else:
+            logging.info(f"Sending {binascii.hexlify(packet.encode())}")
+        self._writer.write(packet.encode())
+
+    async def readPacket(self):
+        while True:
+            rawdata = await self._reader.readuntil(CommsPacket.start_flag)
+            # valid packets are minimum 6 bytes excluding start flag
+            if len(rawdata) >= 6:
+                # replace start flag which was stripped while searching
+                rawdata = CommsPacket.start_flag + rawdata
+                break
+        return CommsPacket(bytearray(rawdata))
+
+    def finishPacket(self, address):
+        self._sequence_send = (self._sequence_send + 1) % 128
+        self._queues[address].task_done()
+        self._acklist[address].set()
+
+    async def recv(self):
+        while self._connected:
+            packet = await self.readPacket()
+
+            try:
+                data = packet.decode()
+            except CommsChecksumError:
+                # checksum failed! wait for it to be resent
+                logging.warning(f"Packet did not match checksum: {packet._data}")
+                continue
+
+            if data is None:
+                # packet is an ack/nack from previously sent data
+                if packet.acked:
+                    logging.info("Received ACK")
+                    # increase packet counter
+                    self.finishPacket(packet.address)
+                else:
+                    logging.debug("Received NACK")
+            else:
+                # packet should contain valid data
+                try:
+                    payload = PayloadFormat.fromByteArray(packet.byteArray)
+                    self.payloadrecv = payload
+                    logging.debug(f"Received payload type {payload.getType()} for timestamp {payload.timestamp}")
+                    comms_response = CommsACK(packet.address)
+                except (StructError, ValueError):
+                    # invalid payload, but valid checksum - this is bad!
+                    logging.error(f"Invalid payload: {payload}")
+                    # restart/reflash/swap to redundant microcontroller?
+                    comms_response = CommsNACK(packet.address)
+                finally:
+                    comms_response.setSequenceReceive(packet.sequence_receive)
+                    await self.sendPacket(comms_response)
+
+    # callback to dependants to read the received payload
+    @property
+    def payloadrecv(self):
+        return self._payloadrecv
+
+    @payloadrecv.setter
+    def payloadrecv(self, payload):
+        for callback in self._observers:
+            # peek at the leftmost item, don't pop until receipt confirmed
+            callback(payload)
+
+    def bind_to(self, callback):
+        self._observers.append(callback)
+
+
+if __name__ == "__main__":
+    try:
+        # schedule async tasks
+        loop = asyncio.get_event_loop()
+
+        # setup serial devices
+        comms = CommsLLI(loop)
+
+        asyncio.gather(comms.main("/dev/ttyUSB0", 115200), return_exceptions=True)
+        loop.run_forever()
+    except asyncio.CancelledError:
+        pass
+    except KeyboardInterrupt:
+        logging.info("Closing LLI")
+    finally:
+        loop.close()
\ No newline at end of file