From a881ac0ed90291878748a7ce37ac66a87be3bbcb Mon Sep 17 00:00:00 2001
From: Andreas Gattringer <andreas.gattringer@univie.ac.at>
Date: Tue, 21 May 2024 22:15:16 +0200
Subject: [PATCH] many updates

---
 LICENSE                                       |   9 +
 TODO.md                                       |   6 +
 .../protocol/compat => external}/__init__.py  |   0
 external/rp_devices.py                        | 177 ++++++++++++++++++
 programs/adc_test.py                          |  67 +++++++
 programs/ae33.py                              |   2 +-
 programs/base_program.py                      |   2 +-
 programs/umnp-rhtp-local.py                   |  45 ++---
 programs/umnp-rhtp.py                         |  43 ++---
 syntheticdata/__init__.py                     |  70 +++++++
 umnp-daq.py                                   |   8 +-
 umnp/daq/file_daq.py                          |  33 +++-
 .../communication/udp_communicator.py         |   2 +-
 .../devices/network/ethernet_w5500.py         |  33 +++-
 umnp/microcontroller/devices/network/udp.py   |   2 +-
 umnp/microcontroller/measurementdevice.py     |  87 ++++++++-
 .../sensors/lps28dfw/__init__.py              |   2 +-
 .../microcontroller/sensors/sht45/__init__.py |   2 +-
 .../microcontroller/umock/machine/__init__.py |   2 +-
 umnp/proto/__init__.py                        |   7 +
 umnp/{protocol => proto}/common/__init__.py   |   0
 umnp/{protocol => proto}/common/timestamp.py  |   2 +-
 .../test.py => umnp/proto/compat/__init__.py  |   0
 umnp/{protocol => proto}/compat/logging.py    |   0
 umnp/{protocol => proto}/constants.py         |   0
 umnp/{protocol => proto}/data_message.py      |   8 +-
 umnp/{protocol => proto}/device.py            |   0
 umnp/proto/device_message.py                  |  68 +++++++
 umnp/{protocol => proto}/device_types.py      |   0
 umnp/{protocol => proto}/headers.py           |   4 +-
 umnp/{protocol => proto}/message.py           |   8 +-
 umnp/{protocol => proto}/message_header.py    |   6 +-
 umnp/{protocol => proto}/messagetype.py       |   4 +-
 umnp/{protocol => proto}/register_messages.py |   2 +-
 umnp/{protocol => proto}/umnp_time.py         |   0
 umnp/protocol/__init__.py                     |   9 -
 umnp/protocol/error_message.py                |  33 ----
 umnp/protocol/info_message.py                 |  33 ----
 38 files changed, 599 insertions(+), 177 deletions(-)
 create mode 100644 LICENSE
 create mode 100644 TODO.md
 rename {umnp/protocol/compat => external}/__init__.py (100%)
 create mode 100644 external/rp_devices.py
 create mode 100644 programs/adc_test.py
 create mode 100644 syntheticdata/__init__.py
 create mode 100644 umnp/proto/__init__.py
 rename umnp/{protocol => proto}/common/__init__.py (100%)
 rename umnp/{protocol => proto}/common/timestamp.py (96%)
 rename programs/test.py => umnp/proto/compat/__init__.py (100%)
 rename umnp/{protocol => proto}/compat/logging.py (100%)
 rename umnp/{protocol => proto}/constants.py (100%)
 rename umnp/{protocol => proto}/data_message.py (79%)
 rename umnp/{protocol => proto}/device.py (100%)
 create mode 100644 umnp/proto/device_message.py
 rename umnp/{protocol => proto}/device_types.py (100%)
 rename umnp/{protocol => proto}/headers.py (63%)
 rename umnp/{protocol => proto}/message.py (93%)
 rename umnp/{protocol => proto}/message_header.py (96%)
 rename umnp/{protocol => proto}/messagetype.py (72%)
 rename umnp/{protocol => proto}/register_messages.py (85%)
 rename umnp/{protocol => proto}/umnp_time.py (100%)
 delete mode 100644 umnp/protocol/__init__.py
 delete mode 100644 umnp/protocol/error_message.py
 delete mode 100644 umnp/protocol/info_message.py

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..984c347
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,9 @@
+This project is licensed under the terms of the GPLv3, unless otherwise stated, especially in the external/ directory.
+
+See the individual files for more information.
+
+
+
+# Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ * external/rp_devices.py: Copyright (c) 2021 Jeremy P Bentham
+ * programs/adc_test.py  based on https://github.com/jbentham/pico/blob/main/rp_adc_test.py, which is Copyright (c) 2021 Jeremy P Bentham
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..a4b2b4e
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,6 @@
+# To do
+
+## Life-sign support
+
+## Network errors
+- Centralize network restart functionality.
\ No newline at end of file
diff --git a/umnp/protocol/compat/__init__.py b/external/__init__.py
similarity index 100%
rename from umnp/protocol/compat/__init__.py
rename to external/__init__.py
diff --git a/external/rp_devices.py b/external/rp_devices.py
new file mode 100644
index 0000000..f292b6d
--- /dev/null
+++ b/external/rp_devices.py
@@ -0,0 +1,177 @@
+# RP2040 uctype definitions for MicroPython
+# See https://iosoft.blog/pico-adc-dma for description
+#
+# Copyright (c) 2021 Jeremy P Bentham
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from uctypes import BF_POS, BF_LEN, UINT32, BFUINT32, struct
+
+GPIO_BASE = 0x40014000
+GPIO_CHAN_WIDTH = 0x08
+GPIO_PIN_COUNT = 30
+PAD_BASE = 0x4001C000
+PAD_PIN_WIDTH = 0x04
+ADC_BASE = 0x4004C000
+DMA_BASE = 0x50000000
+DMA_CHAN_WIDTH = 0x40
+DMA_CHAN_COUNT = 12
+
+# DMA: RP2040 datasheet 2.5.7
+DMA_CTRL_TRIG_FIELDS = {
+    "AHB_ERROR": 31 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "READ_ERROR": 30 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "WRITE_ERROR": 29 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "BUSY": 24 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "SNIFF_EN": 23 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "BSWAP": 22 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "IRQ_QUIET": 21 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "TREQ_SEL": 15 << BF_POS | 6 << BF_LEN | BFUINT32,
+    "CHAIN_TO": 11 << BF_POS | 4 << BF_LEN | BFUINT32,
+    "RING_SEL": 10 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "RING_SIZE": 6 << BF_POS | 4 << BF_LEN | BFUINT32,
+    "INCR_WRITE": 5 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "INCR_READ": 4 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "DATA_SIZE": 2 << BF_POS | 2 << BF_LEN | BFUINT32,
+    "HIGH_PRIORITY": 1 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "EN": 0 << BF_POS | 1 << BF_LEN | BFUINT32,
+}
+# Channel-specific DMA registers
+DMA_CHAN_REGS = {
+    "READ_ADDR_REG": 0x00 | UINT32,
+    "WRITE_ADDR_REG": 0x04 | UINT32,
+    "TRANS_COUNT_REG": 0x08 | UINT32,
+    "CTRL_TRIG_REG": 0x0C | UINT32,
+    "CTRL_TRIG": (0x0C, DMA_CTRL_TRIG_FIELDS),
+}
+# General DMA registers
+DMA_REGS = {
+    "INTR": 0x400 | UINT32,
+    "INTE0": 0x404 | UINT32,
+    "INTF0": 0x408 | UINT32,
+    "INTS0": 0x40C | UINT32,
+    "INTE1": 0x414 | UINT32,
+    "INTF1": 0x418 | UINT32,
+    "INTS1": 0x41C | UINT32,
+    "TIMER0": 0x420 | UINT32,
+    "TIMER1": 0x424 | UINT32,
+    "TIMER2": 0x428 | UINT32,
+    "TIMER3": 0x42C | UINT32,
+    "MULTI_CHAN_TRIGGER": 0x430 | UINT32,
+    "SNIFF_CTRL": 0x434 | UINT32,
+    "SNIFF_DATA": 0x438 | UINT32,
+    "FIFO_LEVELS": 0x440 | UINT32,
+    "CHAN_ABORT": 0x444 | UINT32,
+}
+
+# GPIO status and control: RP2040 datasheet 2.19.6.1.10
+GPIO_STATUS_FIELDS = {
+    "IRQTOPROC": 26 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "IRQFROMPAD": 24 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "INTOPERI": 19 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "INFROMPAD": 17 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "OETOPAD": 13 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "OEFROMPERI": 12 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "OUTTOPAD": 9 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "OUTFROMPERI": 8 << BF_POS | 1 << BF_LEN | BFUINT32,
+}
+GPIO_CTRL_FIELDS = {
+    "IRQOVER": 28 << BF_POS | 2 << BF_LEN | BFUINT32,
+    "INOVER": 16 << BF_POS | 2 << BF_LEN | BFUINT32,
+    "OEOVER": 12 << BF_POS | 2 << BF_LEN | BFUINT32,
+    "OUTOVER": 8 << BF_POS | 2 << BF_LEN | BFUINT32,
+    "FUNCSEL": 0 << BF_POS | 5 << BF_LEN | BFUINT32,
+}
+GPIO_REGS = {
+    "GPIO_STATUS_REG": 0x00 | UINT32,
+    "GPIO_STATUS": (0x00, GPIO_STATUS_FIELDS),
+    "GPIO_CTRL_REG": 0x04 | UINT32,
+    "GPIO_CTRL": (0x04, GPIO_CTRL_FIELDS),
+}
+
+# PAD control: RP2040 datasheet 2.19.6.3
+PAD_FIELDS = {
+    "OD": 7 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "IE": 6 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "DRIVE": 4 << BF_POS | 2 << BF_LEN | BFUINT32,
+    "PUE": 3 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "PDE": 2 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "SCHMITT": 1 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "SLEWFAST": 0 << BF_POS | 1 << BF_LEN | BFUINT32,
+}
+PAD_REGS = {"PAD_REG": 0x00 | UINT32, "PAD": (0x00, PAD_FIELDS)}
+
+# ADC: RP2040 datasheet 4.9.6
+ADC_CS_FIELDS = {
+    "RROBIN": 16 << BF_POS | 5 << BF_LEN | BFUINT32,
+    "AINSEL": 12 << BF_POS | 3 << BF_LEN | BFUINT32,
+    "ERR_STICKY": 10 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "ERR": 9 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "READY": 8 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "START_MANY": 3 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "START_ONCE": 2 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "TS_EN": 1 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "EN": 0 << BF_POS | 1 << BF_LEN | BFUINT32,
+}
+ADC_FCS_FIELDS = {
+    "THRESH": 24 << BF_POS | 4 << BF_LEN | BFUINT32,
+    "LEVEL": 16 << BF_POS | 4 << BF_LEN | BFUINT32,
+    "OVER": 11 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "UNDER": 10 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "FULL": 9 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "EMPTY": 8 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "DREQ_EN": 3 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "ERR": 2 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "SHIFT": 1 << BF_POS | 1 << BF_LEN | BFUINT32,
+    "EN": 0 << BF_POS | 1 << BF_LEN | BFUINT32,
+}
+ADC_REGS = {
+    "CS_REG": 0x00 | UINT32,
+    "CS": (0x00, ADC_CS_FIELDS),
+    "RESULT_REG": 0x04 | UINT32,
+    "FCS_REG": 0x08 | UINT32,
+    "FCS": (0x08, ADC_FCS_FIELDS),
+    "FIFO_REG": 0x0C | UINT32,
+    "DIV_REG": 0x10 | UINT32,
+    "INTR_REG": 0x14 | UINT32,
+    "INTE_REG": 0x18 | UINT32,
+    "INTF_REG": 0x1C | UINT32,
+    "INTS_REG": 0x20 | UINT32,
+}
+DREQ_PIO0_TX0, DREQ_PIO0_RX0, DREQ_PIO1_TX0 = 0, 4, 8
+DREQ_PIO1_RX0, DREQ_SPI0_TX, DREQ_SPI0_RX = 12, 16, 17
+DREQ_SPI1_TX, DREQ_SPI1_RX, DREQ_UART0_TX = 18, 19, 20
+DREQ_UART0_RX, DREQ_UART1_TX, DREQ_UART1_RX = 21, 22, 23
+DREQ_I2C0_TX, DREQ_I2C0_RX, DREQ_I2C1_TX = 32, 33, 34
+DREQ_I2C1_RX, DREQ_ADC = 35, 36
+
+DMA_CHANS = [
+    struct(DMA_BASE + n * DMA_CHAN_WIDTH, DMA_CHAN_REGS)
+    for n in range(0, DMA_CHAN_COUNT)
+]
+DMA_DEVICE = struct(DMA_BASE, DMA_REGS)
+GPIO_PINS = [
+    struct(GPIO_BASE + n * GPIO_CHAN_WIDTH, GPIO_REGS) for n in range(0, GPIO_PIN_COUNT)
+]
+PAD_PINS = [
+    struct(PAD_BASE + n * PAD_PIN_WIDTH, PAD_REGS) for n in range(0, GPIO_PIN_COUNT)
+]
+ADC_DEVICE = struct(ADC_BASE, ADC_REGS)
+ADC_FIFO_ADDR = ADC_BASE + 0x0C
+
+GPIO_FUNC_SPI, GPIO_FUNC_UART, GPIO_FUNC_I2C = 1, 2, 3
+GPIO_FUNC_PWM, GPIO_FUNC_SIO, GPIO_FUNC_PIO0 = 4, 5, 6
+GPIO_FUNC_NULL = 0x1F
+
+# EOF
diff --git a/programs/adc_test.py b/programs/adc_test.py
new file mode 100644
index 0000000..b4f6348
--- /dev/null
+++ b/programs/adc_test.py
@@ -0,0 +1,67 @@
+# based on https://github.com/jbentham/pico/blob/main/rp_adc_test.py
+# which is  Copyright (c) 2021 Jeremy P Bentham and licensed under the Apache License, Version 2.0
+
+import array
+import asyncio
+
+import uctypes
+
+import external.rp_devices as devs
+
+
+class DmaAdc:
+    def __init__(self, pin: int, sample_count: int = 1000, sampling_rate: int = 100000):
+        self._pin_number = pin
+        self._channel_number = pin - 26
+        self._adc = devs.ADC_DEVICE
+        self._pin = devs.GPIO_PINS[pin]
+        self._pad = devs.PAD_PINS[pin]
+        self._pin.GPIO_CTRL_REG = devs.GPIO_FUNC_NULL
+        self._pad.PAD_REG = 0
+        self._buffer = None
+        self._n_samples = sample_count
+        self.set_sample_count(sample_count)
+        self._sampling_rate = sampling_rate
+        self._dma_channel = devs.DMA_CHANS[self._channel_number]
+        self._dma = devs.DMA_DEVICE
+        self._ready = False
+        self._lock = asyncio.Lock()
+
+    def set_sample_count(self, count: int):
+        self._n_samples = count
+        self._buffer = array.array("H", (0 for _ in range(self._n_samples)))
+
+    async def sample(self):
+        with self._lock:
+            self._ready = False
+            self._adc.CS_REG = self._adc.FCS_REG = 0
+            self._adc.CS.EN = 1
+            self._adc.CS.AINSEL = self._channel_number
+            self._adc.FCS.EN = self._adc.FCS.DREQ_EN = 1
+            self._adc.DIV_REG = (48000000 // self._sampling_rate - 1) << 8
+            self._adc.FCS.THRESH = self._adc.FCS.OVER = self._adc.FCS.UNDER = 1
+
+            self._dma_channel.READ_ADDR_REG = devs.ADC_FIFO_ADDR
+            self._dma_channel.WRITE_ADDR_REG = uctypes.addressof(self._buffer)
+            self._dma_channel.TRANS_COUNT_REG = self._n_samples
+
+            self._dma_channel.CTRL_TRIG_REG = 0
+            self._dma_channel.CTRL_TRIG.CHAIN_TO = self._channel_number
+            self._dma_channel.CTRL_TRIG.INCR_WRITE = (
+                self._dma_channel.CTRL_TRIG.IRQ_QUIET
+            ) = 1
+            self._dma_channel.CTRL_TRIG.TREQ_SEL = devs.DREQ_ADC
+            self._dma_channel.CTRL_TRIG.DATA_SIZE = 1
+            self._dma_channel.CTRL_TRIG.EN = 1
+
+            while self._adc.FCS.LEVEL:
+                _ = self._adc.FIFO_REG
+
+            self._adc.CS.START_MANY = 1
+            while self._dma_channel.CTRL_TRIG.BUSY:
+                await asyncio.sleep(10 / 1000)
+
+            self._adc.CS.START_MANY = 0
+            self._dma_channel.CTRL_TRIG.EN = 0
+            self._ready = True
+            return [("%1.3f" % (val * 3.3 / 4096)) for val in self._buffer]
diff --git a/programs/ae33.py b/programs/ae33.py
index 1477b52..9369311 100644
--- a/programs/ae33.py
+++ b/programs/ae33.py
@@ -5,7 +5,7 @@ from umnp.microcontroller.devices.network.ethernet_w5500 import EthernetW5500
 from umnp.microcontroller.devices.network.udp import UDPSender, UDPReceiver
 from umnp.microcontroller.measurementdevice import MeasurementDevice
 from umnp.microcontroller.tasks.periodictask import PeriodicTask
-from umnp.protocol.common import UDP_DATA_PORT, UDP_CMD_PORT
+from umnp.proto.common import UDP_DATA_PORT, UDP_CMD_PORT
 
 if sys.implementation.name == "micropython":
     # noinspection PyUnresolvedReferences
diff --git a/programs/base_program.py b/programs/base_program.py
index 47002bc..96db56c 100644
--- a/programs/base_program.py
+++ b/programs/base_program.py
@@ -5,7 +5,7 @@ from umnp.microcontroller.devices.network.ethernet_w5500 import EthernetW5500
 from umnp.microcontroller.devices.network.udp import UDPSender, UDPReceiver
 from umnp.microcontroller.measurementdevice import MeasurementDevice
 from umnp.microcontroller.tasks.periodictask import PeriodicTask
-from umnp.protocol.common import UDP_DATA_PORT, UDP_CMD_PORT
+from umnp.proto.common import UDP_DATA_PORT, UDP_CMD_PORT
 
 if sys.implementation.name == "micropython":
     # noinspection PyUnresolvedReferences
diff --git a/programs/umnp-rhtp-local.py b/programs/umnp-rhtp-local.py
index 742e8af..b7bd126 100644
--- a/programs/umnp-rhtp-local.py
+++ b/programs/umnp-rhtp-local.py
@@ -6,7 +6,7 @@ from umnp.microcontroller.measurementdevice import MeasurementDevice
 from umnp.microcontroller.sensors.lps28dfw import LPS28DFW
 from umnp.microcontroller.sensors.sht45 import SHT45
 from umnp.microcontroller.tasks.periodictask import PeriodicTask
-from umnp.protocol.data_message import DataMessage
+from umnp.proto.data_message import DataMessage
 
 if sys.implementation.name == "micropython":
     # noinspection PyUnresolvedReferences
@@ -47,54 +47,39 @@ async def aggregate_and_send(
     p, p_t = await p_sensor.measure()
     data = "{:2.2f},{:2.2f},{:2.2f},{:2.2f}".format(t, rh, p, p_t)
     print("Sending: '{}'".format(data))
-    msg = DataMessage(data, device.identifier_raw, device.device_type)
+
     if comm.network_error:
-        print("Network error")
-        spi: machine.SPI = device.spi
-        if spi:
-            print("SPI::deinit()")
-            spi.deinit()
-            spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK)
-            print("SPI::new()")
-            device.add_spi(spi)
-            print("SPI::added()")
-            dev_mac = device.generated_mac_raw()
-            device.network.deactivate()
-            print("ETH:new()")
-            ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP)
-            print("ETH:set()")
-            ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS)
-            print("ETH:added()")
-            device.add_network_adapter(ether)
-            comm.clear_network_error()
+        await device.reset_network()
+
     if not comm.network_error:
+        msg = DataMessage(data, device.identifier_raw, device.device_type)
         await comm.send_message(msg)
-    msg = None
+        msg = None
+
     gc.collect()
 
 
 async def main():
     # configure network
     device = MeasurementDevice(device_type=DEVICE_TYPE_RHTP)
-    spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK)
-    device.add_spi(spi)
-
-    i2c = machine.I2C(id=1, scl=I2C_SCL, sda=I2C_SDA, timeout=10000)
-    device.add_i2c(i2c)
+    spi = device.add_spi(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK)
+    i2c = device.add_i2c(i2c_id=1, scl=I2C_SCL, sda=I2C_SDA, timeout=10000)
 
     dev_mac = device.generated_mac_raw()
     ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP)
     ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS)
-
     device.add_network_adapter(ether)
-    device.add_i2c(i2c)
+
     comm = device.create_communicator()
 
     sht45 = SHT45(i2c)
     p_sensor = LPS28DFW(i2c, i2c_address=0x5C)
 
-    task = PeriodicTask(aggregate_and_send, None, 1000, device, sht45, p_sensor)
-    comm.add_task(task, "aggregate_and_send")
+    comm.add_task(
+        PeriodicTask(aggregate_and_send, None, 1000, device, sht45, p_sensor),
+        "aggregate_and_send",
+    )
+    comm.add_task(PeriodicTask(device.send_life_sign, None, 5000), "life_sign")
 
     asyncio.run(comm.start())
 
diff --git a/programs/umnp-rhtp.py b/programs/umnp-rhtp.py
index e09c853..89bf20d 100644
--- a/programs/umnp-rhtp.py
+++ b/programs/umnp-rhtp.py
@@ -6,7 +6,7 @@ from umnp.microcontroller.measurementdevice import MeasurementDevice
 from umnp.microcontroller.sensors.lps28dfw import LPS28DFW
 from umnp.microcontroller.sensors.sht45 import SHT45
 from umnp.microcontroller.tasks.periodictask import PeriodicTask
-from umnp.protocol.data_message import DataMessage
+from umnp.proto.data_message import DataMessage
 
 if sys.implementation.name == "micropython":
     # noinspection PyUnresolvedReferences
@@ -47,45 +47,27 @@ async def aggregate_and_send(
     p, p_t = await p_sensor.measure()
     data = "{:2.2f},{:2.2f},{:2.2f},{:2.2f}".format(t, rh, p, p_t)
     print("Sending: '{}'".format(data))
-    msg = DataMessage(data, device.identifier_raw, device.device_type)
+
     if comm.network_error:
-        print("Network error")
-        spi: machine.SPI = device.spi
-        if spi:
-            print("SPI::deinit()")
-            spi.deinit()
-            spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK)
-            print("SPI::new()")
-            device.add_spi(spi)
-            print("SPI::added()")
-            dev_mac = device.generated_mac_raw()
-            device.network.deactivate()
-            print("ETH:new()")
-            ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP)
-            print("ETH:set()")
-            ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS)
-            print("ETH:added()")
-            device.add_network_adapter(ether)
-            comm.clear_network_error()
+        await device.reset_network()
+
     if not comm.network_error:
+        msg = DataMessage(data, device.identifier_raw, device.device_type)
         await comm.send_message(msg)
-    msg = None
+        msg = None
+
     gc.collect()
 
 
 async def main():
     # configure network
     device = MeasurementDevice(device_type=DEVICE_TYPE_RHTP)
-    spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK)
-    device.add_spi(spi)
-
-    i2c = machine.I2C(id=1, scl=I2C_SCL, sda=I2C_SDA, timeout=10000)
-    device.add_i2c(i2c)
+    spi = device.add_spi(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK)
+    i2c = device.add_i2c(i2c_id=1, scl=I2C_SCL, sda=I2C_SDA, timeout=10000)
 
     dev_mac = device.generated_mac_raw()
     ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP)
     ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS)
-
     device.add_network_adapter(ether)
     device.add_i2c(i2c)
     comm = device.create_communicator()
@@ -93,8 +75,11 @@ async def main():
     sht45 = SHT45(i2c)
     p_sensor = LPS28DFW(i2c)
 
-    task = PeriodicTask(aggregate_and_send, None, 1000, device, sht45, p_sensor)
-    comm.add_task(task, "aggregate_and_send")
+    comm.add_task(
+        PeriodicTask(aggregate_and_send, None, 1000, device, sht45, p_sensor),
+        "aggregate_and_send",
+    )
+    comm.add_task(PeriodicTask(device.send_life_sign, None, 5000), "life_sign")
 
     asyncio.run(comm.start())
 
diff --git a/syntheticdata/__init__.py b/syntheticdata/__init__.py
new file mode 100644
index 0000000..3da0fcd
--- /dev/null
+++ b/syntheticdata/__init__.py
@@ -0,0 +1,70 @@
+import datetime
+import random
+
+
+class SyntheticDatum:
+    def __init__(self):
+        self.__fields = []
+
+    def get_datum(self, sep=", "):
+        return sep.join(self.__fields)
+
+    def add_value(self, value):
+        self.__fields.append(str(value))
+
+    def add_values(self, values: list | tuple):
+        for field in values:
+            self.__fields.append(str(field))
+
+    def add_time(self, ts: datetime.datetime | None = None):
+        if ts is None:
+            ts = datetime.datetime.now(tz=datetime.UTC)
+        ts = round_to_second(ts)
+        self.__fields.append(str(ts.strftime("%H:%M:%S")))
+
+    def add_date(self, ts: datetime.datetime | None = None, fmt="%Y-%m-%d"):
+        if ts is None:
+            ts = datetime.datetime.now(tz=datetime.UTC)
+        ts = round_to_second(ts)
+        self.__fields.append(str(ts.strftime(fmt)))
+
+    def add_integer(
+        self,
+        min_val: int = 0,
+        max_val: int = 999,
+        count=1,
+    ):
+
+        for i in range(count):
+            value = random.randint(min_val, max_val)
+            self.__fields.append(str(value))
+
+    def add_float(
+        self, min_val: float = 0.0, max_val: float = 1.0, fmt: str = "0.2f", count=1
+    ):
+
+        for i in range(count):
+            value = random.uniform(min_val, max_val)
+            self.__fields.append(f"{value:{fmt}}")
+
+    def add_room_temperature(self, fmt: str = "0.2f", count=1):
+        for i in range(count):
+            value = random.uniform(15, 20)
+            self.__fields.append(f"{value:{fmt}}")
+
+    def add_pressure_pa(self, fmt: str = ".0f", count=1):
+        for i in range(count):
+            value = random.uniform(100000 * 0.95, 100000 * 1.05)
+            self.__fields.append(f"{value:{fmt}}")
+
+
+def round_to_second(when: datetime.datetime | None = None) -> datetime.datetime:
+
+    if when is None:
+        when = datetime.datetime.now(tz=datetime.timezone.utc)
+    if not isinstance(when, datetime.datetime):
+        raise ValueError(f"Timestamp {when} is not a datetime.datetime")
+    if when.microsecond >= 500 * 1000:
+        when = when + datetime.timedelta(seconds=1)
+    now = when.replace(microsecond=0)
+    return now
diff --git a/umnp-daq.py b/umnp-daq.py
index a8d20b5..3410352 100644
--- a/umnp-daq.py
+++ b/umnp-daq.py
@@ -3,8 +3,8 @@ import socket
 
 from umnp.daq.file_daq import FileDAQ
 from umnp.microcontroller.devices.network.udp import DEFAULT_UMNP_DATA_IN_PORT
-from umnp.protocol import DataMessage
-from umnp.protocol.message import Message
+from umnp.proto import DataMessage, DeviceMessage
+from umnp.proto.message import Message
 
 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -21,6 +21,10 @@ def main():
         now = datetime.datetime.now(tz=datetime.timezone.utc)
         if isinstance(msg, DataMessage):
             print(f"{now},{msg.sender_id},{msg.payload()}")
+        elif isinstance(msg, DeviceMessage):
+            print(
+                f"{now},{msg.sender_id},{msg.get_message_type_string},{msg.payload()}"
+            )
         files.save_message(msg)
 
 
diff --git a/umnp/daq/file_daq.py b/umnp/daq/file_daq.py
index ffaaf0d..c768f70 100644
--- a/umnp/daq/file_daq.py
+++ b/umnp/daq/file_daq.py
@@ -2,8 +2,13 @@ import datetime
 import os
 
 from umnp.devices import get_device_type_name
-from umnp.protocol.headers import MESSAGE_HEADERS
-from umnp.protocol.message import Message
+from umnp.proto import DataMessage, DeviceMessage
+from umnp.proto.headers import DEVICE_HEADERS
+from umnp.proto.message import Message
+
+
+def clean(field: str) -> str:
+    return field.replace(",", ";")
 
 
 class FileDAQ:
@@ -27,9 +32,19 @@ class FileDAQ:
         self.__open_files[file_id] = open(filename, "w")
         f = self.__open_files[file_id]
         self.__open_fns[file_id] = filename
-        header = MESSAGE_HEADERS.get(msg.sender_type, {}).get(msg.type)
+        headers = ["receive-time", "sender-id"]
+        if isinstance(msg, DataMessage):
+            headers.extend(DEVICE_HEADERS.get(msg.sender_type, {}).get(msg.type))
+
+        elif isinstance(msg, DeviceMessage):
+            headers.extend(msg.header_fields)
+
+        else:
+            headers = ["# unknown"]
+        headers = [header.replace(",", ";") for header in headers]
+        header = ",".join(headers)
         if header:
-            f.write("receive-time,sender-id," + ",".join(header) + "\n")
+            f.write(header + "\n")
         return f
 
     def save_message(self, msg: Message):
@@ -61,5 +76,13 @@ class FileDAQ:
                 f.flush()
 
         if f:
-            f.write(f"{when},{msg.sender_id},{msg.payload()}\n")
+            if isinstance(msg, DataMessage):
+                payload = clean(msg.payload())
+                f.write(f"{when},{msg.sender_id},{payload}\n")
+            elif isinstance(msg, DeviceMessage):
+                payload = clean(msg.payload())
+                msg_type = clean(msg.get_message_type_string)
+                f.write(f"{when},{msg.sender_id},{msg_type},{payload}\n")
+            else:
+                f.write(msg.payload + "\n")
             f.flush()
diff --git a/umnp/microcontroller/communication/udp_communicator.py b/umnp/microcontroller/communication/udp_communicator.py
index c9346cc..a713209 100644
--- a/umnp/microcontroller/communication/udp_communicator.py
+++ b/umnp/microcontroller/communication/udp_communicator.py
@@ -3,7 +3,7 @@ import time
 
 from umnp.microcontroller.devices.network.udp import UDPSender, UDPReceiver
 from umnp.microcontroller.tasks.periodictask import PeriodicTask
-from umnp.protocol.message import Message
+from umnp.proto.message import Message
 
 if sys.implementation.name == "micropython":
     # noinspection PyUnresolvedReferences
diff --git a/umnp/microcontroller/devices/network/ethernet_w5500.py b/umnp/microcontroller/devices/network/ethernet_w5500.py
index 01e6b8c..711ed48 100644
--- a/umnp/microcontroller/devices/network/ethernet_w5500.py
+++ b/umnp/microcontroller/devices/network/ethernet_w5500.py
@@ -9,7 +9,7 @@ if sys.implementation.name == "micropython":
 
     # noinspection PyUnresolvedReferences
     import network
-    import umnp.protocol.compat.logging as logging
+    import umnp.proto.compat.logging as logging
 
 else:
     from umnp.microcontroller.umock import machine, network
@@ -26,16 +26,43 @@ class EthernetW5500(EthernetAdapter):
         dhcp=False,
     ):
         super().__init__()
+        self._dhcp = dhcp
         self._spi = spi
+        self._mac = mac
         self._pin1 = pin_cs
         self._pin2 = pin_rst
+        self._nic = None
+        self._ip = None
+        self._subnet = None
+        self._gateway = None
+        self._dns = None
+        self._init(spi)
+
+        # self._nic = network.WIZNET5K(spi, self._pin1, self._pin2)
+        # self._nic.active(True)
+
+        # self._nic.config(mac=self._mac)
+        # if self._dhcp:
+        #    self.enable_dhcp()
+
+    def _init(self, spi: machine.SPI):
         self._nic = network.WIZNET5K(spi, self._pin1, self._pin2)
         self._nic.active(True)
-        self._nic.config(mac=mac)
-        if dhcp:
+        self._nic.config(mac=self._mac)
+        if self._dhcp:
             self.enable_dhcp()
 
+    def reinit(self, spi: machine.SPI):
+        self._init(spi)
+        self.set_network(
+            ip=self._ip, subnet_mask=self._subnet, gateway=self._gateway, dns=self._dns
+        )
+
     def set_network(self, ip: str, subnet_mask: str, gateway: str, dns: str):
+        self._ip = ip
+        self._subnet = subnet_mask
+        self._gateway = gateway
+        self._dns = dns
         self._nic.ifconfig((ip, subnet_mask, gateway, dns))
 
     def enable_dhcp(self):
diff --git a/umnp/microcontroller/devices/network/udp.py b/umnp/microcontroller/devices/network/udp.py
index ef95620..53955be 100644
--- a/umnp/microcontroller/devices/network/udp.py
+++ b/umnp/microcontroller/devices/network/udp.py
@@ -10,7 +10,7 @@ from umnp.microcontroller.devices.network import (
 if sys.implementation.name == "micropython":
     # noinspection PyUnresolvedReferences
     import uasyncio as asyncio
-    import umnp.protocol.compat.logging as logging
+    import umnp.proto.compat.logging as logging
 
 else:
     import logging
diff --git a/umnp/microcontroller/measurementdevice.py b/umnp/microcontroller/measurementdevice.py
index 9a1ab8f..72aba17 100644
--- a/umnp/microcontroller/measurementdevice.py
+++ b/umnp/microcontroller/measurementdevice.py
@@ -10,7 +10,9 @@ from umnp.microcontroller.devices.network.udp import (
     DEFAULT_UMNP_DATA_IN_PORT,
     DEFAULT_UMNP_COMMAND_IN_PORT,
 )
-from umnp.protocol.constants import MSG_STRING_ENCODING
+from umnp.proto import DeviceMessage
+from umnp.proto.constants import MSG_STRING_ENCODING
+from umnp.proto.device_message import MSG_TYPE_LIFE_SIGN, MSG_TYPE_INFO
 
 if sys.implementation.name == "micropython":
     # noinspection PyUnresolvedReferences
@@ -39,6 +41,7 @@ class MeasurementDevice:
         self._type = device_type
         self._i2c = None
         self._spi = None
+        self._spi_params = None
 
     @property
     def device_type(self):
@@ -48,15 +51,49 @@ class MeasurementDevice:
     def boot_time(self):
         return self._boot_time
 
-    def add_i2c(self, i2c: machine.I2C):
-        self._i2c = i2c
-
     @property
     def spi(self):
         return self._spi
 
-    def add_spi(self, spi: machine.SPI):
-        self._spi = spi
+    def spi_reinit(self) -> machine.SPI:
+        if len(self._spi_params) == 0:
+            return
+
+        if self._spi:
+            print("SPI::reinit()")
+            self._spi.deinit()
+        self._spi = machine.SPI(
+            self._spi_params["id"],
+            self._spi_params["baud"],
+            mosi=self._spi_params["mosi"],
+            miso=self._spi_params["miso"],
+            sck=self._spi_params["sck"],
+        )
+        return self._spi
+
+    def add_spi(
+        self,
+        spi_id: int,
+        baudrate: int,
+        mosi: machine.Pin,
+        miso: machine.Pin,
+        sck: machine.Pin,
+    ) -> machine.SPI:
+
+        self._spi_params = {
+            "id": spi_id,
+            "baud": baudrate,
+            "mosi": mosi,
+            "miso": miso,
+            "sck": sck,
+        }
+
+        self._spi = self.spi_reinit()
+        return self._spi
+
+    def add_i2c(self, i2c_id: int, scl: machine.Pin, sda: machine.Pin, timeout: int):
+        self._i2c = machine.I2C(id=i2c_id, scl=scl, sda=sda, timeout=timeout)
+        return self._i2c
 
     @property
     def i2c(self):
@@ -65,9 +102,23 @@ class MeasurementDevice:
     def add_network_adapter(self, adapter):
         self._network = adapter
 
-    def reset_network_adapter(self):
-        if self._network:
-            self._network.reset()
+    async def reset_network(self):
+        print("Network error")
+        if self.spi:
+            self.spi_reinit()
+            # dev_mac = device.generated_mac_raw()
+            self.network.deactivate()
+            self.network.reinit(self.spi)
+            self.communicator.clear_network_error()
+
+            info_msg = DeviceMessage(
+                "restarted network",
+                MSG_TYPE_INFO,
+                self.identifier_raw,
+                self.device_type,
+            )
+            await self.communicator.send_message(info_msg)
+            info_msg = None
 
     @property
     def network(self):
@@ -118,3 +169,21 @@ class MeasurementDevice:
     @property
     def communicator(self) -> UDPCommunicator | None:
         return self._communicator
+
+    async def send_life_sign(self):
+        if not self.communicator:
+            return
+        if self.communicator.network_error:
+            return
+
+        now = time.time()
+        seconds_since_boot = now - self.boot_time
+
+        msg = DeviceMessage(
+            f"{seconds_since_boot}",
+            MSG_TYPE_LIFE_SIGN,
+            self.identifier_raw,
+            self.device_type,
+        )
+
+        await self.communicator.send_message(msg)
diff --git a/umnp/microcontroller/sensors/lps28dfw/__init__.py b/umnp/microcontroller/sensors/lps28dfw/__init__.py
index 11e55ad..4e10bdc 100644
--- a/umnp/microcontroller/sensors/lps28dfw/__init__.py
+++ b/umnp/microcontroller/sensors/lps28dfw/__init__.py
@@ -9,7 +9,7 @@ except ImportError:
     from umnp.microcontroller.umock.machine import I2C
 
 if sys.implementation.name == "micropython":
-    import umnp.protocol.compat.logging as logging
+    import umnp.proto.compat.logging as logging
 else:
 
     def const(x):
diff --git a/umnp/microcontroller/sensors/sht45/__init__.py b/umnp/microcontroller/sensors/sht45/__init__.py
index 40da97f..845b519 100644
--- a/umnp/microcontroller/sensors/sht45/__init__.py
+++ b/umnp/microcontroller/sensors/sht45/__init__.py
@@ -7,7 +7,7 @@ except ImportError:
 
 
 if sys.implementation.name == "micropython":
-    import umnp.protocol.compat.logging as logging
+    import umnp.proto.compat.logging as logging
 else:
 
     def const(x):
diff --git a/umnp/microcontroller/umock/machine/__init__.py b/umnp/microcontroller/umock/machine/__init__.py
index 178916d..7203546 100644
--- a/umnp/microcontroller/umock/machine/__init__.py
+++ b/umnp/microcontroller/umock/machine/__init__.py
@@ -1,7 +1,7 @@
 import datetime
 import uuid
 
-from umnp.protocol.message_header import MSG_BYTE_ORDER
+from umnp.proto.message_header import MSG_BYTE_ORDER
 
 
 def unique_id() -> bytes:
diff --git a/umnp/proto/__init__.py b/umnp/proto/__init__.py
new file mode 100644
index 0000000..bd3c4af
--- /dev/null
+++ b/umnp/proto/__init__.py
@@ -0,0 +1,7 @@
+from umnp.proto.data_message import DataMessage
+from umnp.proto.device_message import DeviceMessage
+from umnp.proto.messagetype import MessageType
+from umnp.proto.register_messages import register_messages
+
+register_messages(MessageType.MSG_DEVICE_DATA, DataMessage, "data")
+register_messages(MessageType.MSG_DEVICE_INFO, DeviceMessage, "info")
diff --git a/umnp/protocol/common/__init__.py b/umnp/proto/common/__init__.py
similarity index 100%
rename from umnp/protocol/common/__init__.py
rename to umnp/proto/common/__init__.py
diff --git a/umnp/protocol/common/timestamp.py b/umnp/proto/common/timestamp.py
similarity index 96%
rename from umnp/protocol/common/timestamp.py
rename to umnp/proto/common/timestamp.py
index 0240240..aff9f03 100644
--- a/umnp/protocol/common/timestamp.py
+++ b/umnp/proto/common/timestamp.py
@@ -1,6 +1,6 @@
 import sys
 
-from umnp.protocol.umnp_time import seconds_since_epoch
+from umnp.proto.umnp_time import seconds_since_epoch
 
 if sys.implementation.name == "micropython":
     pass
diff --git a/programs/test.py b/umnp/proto/compat/__init__.py
similarity index 100%
rename from programs/test.py
rename to umnp/proto/compat/__init__.py
diff --git a/umnp/protocol/compat/logging.py b/umnp/proto/compat/logging.py
similarity index 100%
rename from umnp/protocol/compat/logging.py
rename to umnp/proto/compat/logging.py
diff --git a/umnp/protocol/constants.py b/umnp/proto/constants.py
similarity index 100%
rename from umnp/protocol/constants.py
rename to umnp/proto/constants.py
diff --git a/umnp/protocol/data_message.py b/umnp/proto/data_message.py
similarity index 79%
rename from umnp/protocol/data_message.py
rename to umnp/proto/data_message.py
index b59994a..8640733 100644
--- a/umnp/protocol/data_message.py
+++ b/umnp/proto/data_message.py
@@ -1,7 +1,7 @@
-from umnp.protocol.common.timestamp import TimeStamp
-from umnp.protocol.constants import MSG_STRING_ENCODING
-from umnp.protocol.message import Message, MessageHeader
-from umnp.protocol.messagetype import MessageType
+from umnp.proto.common.timestamp import TimeStamp
+from umnp.proto.constants import MSG_STRING_ENCODING
+from umnp.proto.message import Message, MessageHeader
+from umnp.proto.messagetype import MessageType
 
 
 class DataMessage(Message):
diff --git a/umnp/protocol/device.py b/umnp/proto/device.py
similarity index 100%
rename from umnp/protocol/device.py
rename to umnp/proto/device.py
diff --git a/umnp/proto/device_message.py b/umnp/proto/device_message.py
new file mode 100644
index 0000000..5e61285
--- /dev/null
+++ b/umnp/proto/device_message.py
@@ -0,0 +1,68 @@
+from umnp.proto.common.timestamp import TimeStamp
+from umnp.proto.constants import MSG_STRING_ENCODING
+from umnp.proto.message import Message, MessageHeader, MSG_BYTE_ORDER
+from umnp.proto.messagetype import MessageType
+
+MSG_TYPE_UNKNOWN = 0
+MSG_TYPE_ERROR = 1
+MSG_TYPE_INFO = 2
+MSG_TYPE_LIFE_SIGN = 3
+
+
+_MSG_TYPE_DICT = {
+    MSG_TYPE_ERROR: "error",
+    MSG_TYPE_INFO: "info",
+    MSG_TYPE_LIFE_SIGN: "seconds since boot",
+}
+
+
+class DeviceMessage(Message):
+    def __init__(
+        self,
+        data: str,
+        msg_type: int,
+        sender_id: bytes,
+        sender_type: int,
+        send_time: TimeStamp = None,
+    ):
+        self._msg_type = msg_type
+        self._payload = data
+        _msg_type = msg_type.to_bytes(1, MSG_BYTE_ORDER)
+        self._encoded_data = _msg_type + data.encode(MSG_STRING_ENCODING)
+        super().__init__(
+            MessageType.MSG_DEVICE_INFO,
+            self._encoded_data,
+            sender_id,
+            sender_type,
+            send_time,
+        )
+
+    @staticmethod
+    def _decode_payload(transferred_data):
+        return transferred_data.decode(MSG_STRING_ENCODING)
+
+    @property
+    def get_message_type_string(self):
+        return _MSG_TYPE_DICT.get(self._msg_type, "unknown")
+
+    @property
+    def message_type(self):
+        return self._msg_type
+
+    def payload(self) -> str:
+        return self._payload
+
+    @classmethod
+    def decode(cls, payload: bytes, header: MessageHeader) -> "DeviceMessage":
+        decoded_payload = cls._decode_payload(payload[1:])
+        return cls(
+            decoded_payload,
+            payload[0],
+            header.sender_id,
+            header.sender_type,
+            header.timestamp,
+        )
+
+    @property
+    def header_fields(self):
+        return ["message-type", "message"]
diff --git a/umnp/protocol/device_types.py b/umnp/proto/device_types.py
similarity index 100%
rename from umnp/protocol/device_types.py
rename to umnp/proto/device_types.py
diff --git a/umnp/protocol/headers.py b/umnp/proto/headers.py
similarity index 63%
rename from umnp/protocol/headers.py
rename to umnp/proto/headers.py
index d868e09..3441dd1 100644
--- a/umnp/protocol/headers.py
+++ b/umnp/proto/headers.py
@@ -1,6 +1,6 @@
 from umnp.devices import DEVICE_TYPE_RHTP
-from umnp.protocol.messagetype import MessageType
+from umnp.proto.messagetype import MessageType
 
-MESSAGE_HEADERS = {
+DEVICE_HEADERS = {
     DEVICE_TYPE_RHTP: {MessageType.MSG_DEVICE_DATA: ["T", "rH", "p", "T(p)"]}
 }
diff --git a/umnp/protocol/message.py b/umnp/proto/message.py
similarity index 93%
rename from umnp/protocol/message.py
rename to umnp/proto/message.py
index f18431f..afbc8da 100644
--- a/umnp/protocol/message.py
+++ b/umnp/proto/message.py
@@ -7,12 +7,12 @@ except ImportError:
 try:
     import logging
 except ImportError:
-    import umnp.protocol.compat.logging as logging
+    from umnp.proto.compat import logging
 
 
-from umnp.protocol.common.timestamp import TimeStamp
-from umnp.protocol.constants import MSG_BYTE_ORDER, MSG_LEN_PAYLOAD_SIZE
-from umnp.protocol.message_header import MessageHeader
+from umnp.proto.common.timestamp import TimeStamp
+from umnp.proto.constants import MSG_BYTE_ORDER, MSG_LEN_PAYLOAD_SIZE
+from umnp.proto.message_header import MessageHeader
 
 
 class Message:
diff --git a/umnp/protocol/message_header.py b/umnp/proto/message_header.py
similarity index 96%
rename from umnp/protocol/message_header.py
rename to umnp/proto/message_header.py
index e13a2f6..bdda79c 100644
--- a/umnp/protocol/message_header.py
+++ b/umnp/proto/message_header.py
@@ -1,5 +1,5 @@
-from umnp.protocol.common.timestamp import TimeStamp, valid_timestamp
-from umnp.protocol.constants import (
+from umnp.proto.common.timestamp import TimeStamp, valid_timestamp
+from umnp.proto.constants import (
     MSG_PROTOCOL_VERSION,
     MSG_LEN_PROTOCOL_VERSION,
     MSG_BYTE_ORDER,
@@ -12,7 +12,7 @@ from umnp.protocol.constants import (
 try:
     import logging
 except ImportError:
-    import umnp.protocol.compat.logging as logging
+    from umnp.proto.compat import logging
 
 try:
     # noinspection PyUnresolvedReferences
diff --git a/umnp/protocol/messagetype.py b/umnp/proto/messagetype.py
similarity index 72%
rename from umnp/protocol/messagetype.py
rename to umnp/proto/messagetype.py
index b180fd9..077f102 100644
--- a/umnp/protocol/messagetype.py
+++ b/umnp/proto/messagetype.py
@@ -3,11 +3,11 @@
 
 class MessageType:
     MSG_DEVICE_DATA = 1
-    MSG_TYPE_INFO = 16
+    MSG_DEVICE_INFO = 16
     MSG_TYPE_ERROR = 32
     MSG_TYPE_UNKNOWN = 65535
 
-    _allowed_message_types = [MSG_DEVICE_DATA, MSG_TYPE_UNKNOWN]
+    _allowed_message_types = [MSG_DEVICE_DATA, MSG_TYPE_UNKNOWN, MSG_DEVICE_INFO]
 
     @staticmethod
     def valid_message_type(msg_type: int):
diff --git a/umnp/protocol/register_messages.py b/umnp/proto/register_messages.py
similarity index 85%
rename from umnp/protocol/register_messages.py
rename to umnp/proto/register_messages.py
index 3e8c8dc..b29898b 100644
--- a/umnp/protocol/register_messages.py
+++ b/umnp/proto/register_messages.py
@@ -3,7 +3,7 @@ try:
     import typing
 except ImportError:
     pass
-from umnp.protocol.message import Message
+from umnp.proto.message import Message
 
 MESSAGE_NAMES = {}
 
diff --git a/umnp/protocol/umnp_time.py b/umnp/proto/umnp_time.py
similarity index 100%
rename from umnp/protocol/umnp_time.py
rename to umnp/proto/umnp_time.py
diff --git a/umnp/protocol/__init__.py b/umnp/protocol/__init__.py
deleted file mode 100644
index a7f0d18..0000000
--- a/umnp/protocol/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from umnp.protocol.data_message import DataMessage
-from umnp.protocol.error_message import ErrorMessage
-from umnp.protocol.info_message import InfoMessage
-from umnp.protocol.messagetype import MessageType
-from umnp.protocol.register_messages import register_messages
-
-register_messages(MessageType.MSG_DEVICE_DATA, DataMessage, "data")
-register_messages(MessageType.MSG_TYPE_INFO, InfoMessage, "info")
-register_messages(MessageType.MSG_TYPE_ERROR, ErrorMessage, "error")
diff --git a/umnp/protocol/error_message.py b/umnp/protocol/error_message.py
deleted file mode 100644
index 314332f..0000000
--- a/umnp/protocol/error_message.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from umnp.protocol.common.timestamp import TimeStamp
-from umnp.protocol.constants import MSG_STRING_ENCODING
-from umnp.protocol.message import Message, MessageHeader
-from umnp.protocol.messagetype import MessageType
-
-
-class ErrorMessage(Message):
-    def __init__(
-        self, data: str, sender_id: bytes, sender_type: int, send_time: TimeStamp = None
-    ):
-        self._payload = data
-        self._encoded_data = data.encode(MSG_STRING_ENCODING)
-        super().__init__(
-            MessageType.MSG_DEVICE_ERROR,
-            self._encoded_data,
-            sender_id,
-            sender_type,
-            send_time,
-        )
-
-    @staticmethod
-    def _decode_payload(transferred_data):
-        return transferred_data.decode(MSG_STRING_ENCODING)
-
-    def payload(self) -> str:
-        return self._payload
-
-    @classmethod
-    def decode(cls, payload: bytes, header: MessageHeader) -> "ErrorMessage":
-        decoded_payload = cls._decode_payload(payload)
-        return cls(
-            decoded_payload, header.sender_id, header.sender_type, header.timestamp
-        )
diff --git a/umnp/protocol/info_message.py b/umnp/protocol/info_message.py
deleted file mode 100644
index b8a45b2..0000000
--- a/umnp/protocol/info_message.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from umnp.protocol.common.timestamp import TimeStamp
-from umnp.protocol.constants import MSG_STRING_ENCODING
-from umnp.protocol.message import Message, MessageHeader
-from umnp.protocol.messagetype import MessageType
-
-
-class InfoMessage(Message):
-    def __init__(
-        self, data: str, sender_id: bytes, sender_type: int, send_time: TimeStamp = None
-    ):
-        self._payload = data
-        self._encoded_data = data.encode(MSG_STRING_ENCODING)
-        super().__init__(
-            MessageType.MSG_DEVICE_INFO,
-            self._encoded_data,
-            sender_id,
-            sender_type,
-            send_time,
-        )
-
-    @staticmethod
-    def _decode_payload(transferred_data):
-        return transferred_data.decode(MSG_STRING_ENCODING)
-
-    def payload(self) -> str:
-        return self._payload
-
-    @classmethod
-    def decode(cls, payload: bytes, header: MessageHeader) -> "InfoMessage":
-        decoded_payload = cls._decode_payload(payload)
-        return cls(
-            decoded_payload, header.sender_id, header.sender_type, header.timestamp
-        )
-- 
GitLab