diff --git a/programs/umnp-rhtp.py b/programs/umnp-rhtp.py
index ee04d853faf924642d02d829e16d6c4b9ee083d2..4d40c7144d69154573ead2bad1bffa862b697399 100644
--- a/programs/umnp-rhtp.py
+++ b/programs/umnp-rhtp.py
@@ -17,6 +17,26 @@ if sys.implementation.name == "micropython":
 else:
     from umnp.microcontroller.umock import machine
 
+# SPI PARAMETERS
+SPI_BAUD = 2_000_000
+SPI_MOSI = machine.Pin(19)
+SPI_MISO = machine.Pin(16)
+SPI_SCK = machine.Pin(18)  # serial clock
+
+# I2C PARAMETERS
+I2C_SCL = machine.Pin(4)  # serial clock
+I2C_SDA = machine.Pin(5)  # serial data
+
+# ETHERNET PARAMETERS
+ETH_CS = machine.Pin(17)  # chip select
+ETH_RST = machine.Pin(20)  # reset
+
+ETH_IP = "192.168.0.33"  # IP of microcontroller
+ETH_SUBNET = "255.255.255.0"  # ...
+ETH_GATEWAY = "192.168.0.1"  # ...
+ETH_DNS = "192.168.0.2"  # technically not necessary
+ETH_USE_DHCP = False
+
 
 async def aggregate_and_send(
     device: MeasurementDevice, sht45: SHT45, p_sensor: LPS28DFW
@@ -25,6 +45,7 @@ async def aggregate_and_send(
     t, rh = await sht45.measure()
     p, p_t = await p_sensor.measure()
     data = f"{t},{rh},{p},{p_t}"
+    print(f"Sending: '{data}'")
     msg = DataMessage(data, device.identifier_raw, device.device_type)
     await comm.send_message(msg)
 
@@ -32,25 +53,13 @@ async def aggregate_and_send(
 async def main():
     # configure network
     device = MeasurementDevice(device_type=DEVICE_TYPE_RHTP)
-    spi = machine.SPI(
-        0, 2_000_000, mosi=machine.Pin(19), miso=machine.Pin(16), sck=machine.Pin(18)
-    )
-
-    i2c = machine.I2C(id=1, scl=machine.Pin(5), sda=machine.Pin(4))
-    ether = EthernetW5500(
-        spi,
-        machine.Pin(17),
-        machine.Pin(20),
-        mac=device.generated_mac_raw(),
-        dhcp=False,
-    )
-
-    ip = "192.168.0.33"  # IP of computer with receiver software FIXME name
-    subnet_mask = "255.255.255.0"  # ...
-    gateway = "192.168.0.1"  # ...
-    dns = "192.168.0.2"  # technically not necessary
-
-    ether.set_network(ip, subnet_mask, gateway, dns)
+    spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK)
+    i2c = machine.I2C(id=1, scl=I2C_SCL, sda=I2C_SDA)
+
+    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)
     comm = device.create_communicator()
 
@@ -59,7 +68,7 @@ async def main():
 
     task = PeriodicTask(aggregate_and_send, None, 1000, device, sht45, p_sensor)
     comm.add_task(task, "aggregate_and_send")
-    # start
+
     asyncio.run(comm.start())
 
 
diff --git a/umnp-daq.py b/umnp-daq.py
index 555240866320849683952a0a1d58dfc05d381ab6..56315de86bb61144348c2919aa2d6fc5e744b76d 100644
--- a/umnp-daq.py
+++ b/umnp-daq.py
@@ -10,6 +10,7 @@ sock.bind(("0.0.0.0", DEFAULT_UMNP_DATA_IN_PORT))
 
 while True:
     data, addr = sock.recvfrom(2048)
+
     msg = Message.from_bytes(data)
     if isinstance(msg, DataMessage):
-        print(msg.payload())
+        print(msg.sender_id, msg.payload())
diff --git a/umnp/microcontroller/communication/serial_uart.py b/umnp/microcontroller/communication/serial_uart.py
index d943879eee3f3914f8ef7eb7cd33adbfbd0aa161..ba87c620dc4b08c35abc43be7e1ab6f83277c09e 100644
--- a/umnp/microcontroller/communication/serial_uart.py
+++ b/umnp/microcontroller/communication/serial_uart.py
@@ -2,7 +2,7 @@ import sys
 
 from umnp.communication.abstract_serial_connection import AbstractSerialConnection
 
-if sys.implementation == "micropython":
+if sys.implementation.name == "micropython":
     import machine
 else:
     from umnp.microcontroller.umock import machine
diff --git a/umnp/microcontroller/devices/network/ethernet_w5500.py b/umnp/microcontroller/devices/network/ethernet_w5500.py
index 29de738914523c145384913ac677f95b2abeb079..23bddc21438dd06db97f207c4a212a5a91977e5e 100644
--- a/umnp/microcontroller/devices/network/ethernet_w5500.py
+++ b/umnp/microcontroller/devices/network/ethernet_w5500.py
@@ -16,15 +16,15 @@ class EthernetW5500(EthernetAdapter):
     def __init__(
         self,
         spi: machine.SPI,
-        pin1: machine.Pin,
-        pin2: machine.Pin,
+        pin_cs: machine.Pin,
+        pin_rst: machine.Pin,
         mac: bytes,
         dhcp=False,
     ):
         super().__init__()
         self._spi = spi
-        self._pin1 = pin1
-        self._pin2 = pin2
+        self._pin1 = pin_cs
+        self._pin2 = pin_rst
         self._nic = network.WIZNET5K(spi, self._pin1, self._pin2)
         self._nic.active(True)
         self._nic.config(mac=mac)
diff --git a/umnp/microcontroller/measurementdevice.py b/umnp/microcontroller/measurementdevice.py
index 45330d15ae4430ee2339cbad36f947c31cd02fe6..9a3aef14fca9fd88b3af06d8267aaacec571028d 100644
--- a/umnp/microcontroller/measurementdevice.py
+++ b/umnp/microcontroller/measurementdevice.py
@@ -23,6 +23,12 @@ class MeasurementDevice:
     def __init__(self, device_type: int = DEVICE_TYPE_UNKNOWN):
         self._boot_time = time.time()
         self._identifier_raw = machine.unique_id()
+        if len(self._identifier_raw) > 6:
+            self._identifier_raw = self._identifier_raw[:6]
+        elif len(self._identifier_raw) < 6:
+            self._identifier_raw = (
+                b"0" * (6 - len(self._identifier_raw)) + self._identifier_raw
+            )
         self._identifier = binascii.hexlify(self.identifier_raw).decode(
             MSG_STRING_ENCODING
         )
@@ -82,9 +88,7 @@ class MeasurementDevice:
         if not self._communicator:
             s = self.create_sender(port=data_port)
             r = self.create_receiver(port=cmd_port)
-            self._communicator = UDPCommunicator(
-                receiver=r, sender=s, device_id=self.identifier
-            )
+            self._communicator = UDPCommunicator(receiver=r, sender=s, device=self)
         return self._communicator
 
     @property
diff --git a/umnp/microcontroller/tasks/periodictask.py b/umnp/microcontroller/tasks/periodictask.py
index bf37767637cb091aba9667a3fca5b6f82dafeeaf..ab0aa949ccbe07af47a0ef3e39907fa583073905 100644
--- a/umnp/microcontroller/tasks/periodictask.py
+++ b/umnp/microcontroller/tasks/periodictask.py
@@ -26,17 +26,15 @@ class PeriodicTask:
         delay_seconds = 25 / 1000
 
         while True:
-            last = time.time()
-            print("A0", args)
-            print("A1", *args)
+            last = time.ticks_ms()
             result = await func(*args)
-            print(result)
             if call_back:
                 await call_back(result)
 
             while True:
                 now = time.ticks_ms()
-                diff = time.ticks_diff(last, now)
+                diff = time.ticks_diff(now, last)
+                # print(last, now, diff)
                 if diff > self._every_ms:
                     last = now
                     break
diff --git a/umnp/protocol/common/timestamp.py b/umnp/protocol/common/timestamp.py
index f9f763546c480c1dec07851edc140a46abc8260e..0240240112bedf0cb0ee294a206d0e59bf3c361f 100644
--- a/umnp/protocol/common/timestamp.py
+++ b/umnp/protocol/common/timestamp.py
@@ -1,31 +1,19 @@
 import sys
-import time
-
-try:
-    import datetime
-except ImportError:
-    from umnp.protocol.compat.datetime import datetime
-
-
-if sys.implementation == "micropython":
-    # noinspection PyUnresolvedReferences
-    import machine
 
+from umnp.protocol.umnp_time import seconds_since_epoch
 
-"""
-        rtc = machine.RTC()
-        now = rtc.datetime()
-        year, month, day, hour, minute, second, second_fraction = now
-"""
+if sys.implementation.name == "micropython":
+    pass
+else:
+    import datetime
 
 
 def ts_utc_now_second() -> int:
-    if sys.implementation == "micropython":
-        rtc = machine.RTC()
+    if sys.implementation.name == "micropython":
+        # rtc = machine.RTC()
         # https://docs.micropython.org/en/latest/library/time.html#module-time
         # Micropython's time.time() returns the number of seconds, as an integer, since the Epoch
-        return time.time()
-
+        return seconds_since_epoch()
     else:
         ts = datetime.datetime.now(tz=datetime.timezone.utc).timestamp()
         return int(round(ts))
@@ -33,14 +21,20 @@ def ts_utc_now_second() -> int:
 
 class TimeStamp:
     def __init__(self, when=None):
-        if when:
+        if when is None:
             self._ts = ts_utc_now_second()
-        else:
+        elif isinstance(when, TimeStamp):
+            self._ts = when.value
+        elif isinstance(when, int):
             self._ts = when
+        else:
+            error = f"Could not convert time {when} (type {type(when)} to TimeStamp."
+            error += "Allowed values are integers > 0, TimeStamp objects or None."
+            raise ValueError(error)
 
         if not isinstance(self._ts, int):
             raise ValueError("TimeStamp not an integer")
-        if not self._ts >= 0:
+        if self._ts < 0:
             raise ValueError("TimeStamp < 0")
 
     @property
@@ -53,6 +47,6 @@ def valid_timestamp(when: TimeStamp | None) -> TimeStamp:
         return TimeStamp()
 
     if not isinstance(when, TimeStamp):
-        raise ValueError("Expected None TimeStamp")
+        raise ValueError("Expected None or TimeStamp")
 
     return when
diff --git a/umnp/protocol/compat/datetime.py b/umnp/protocol/compat/datetime.py
deleted file mode 100644
index 62fafebd666a6af8c6cea63ab75a62b6fa484a0d..0000000000000000000000000000000000000000
--- a/umnp/protocol/compat/datetime.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import time
-
-try:
-    # noinspection PyUnresolvedReferences
-    import machine
-except ImportError:
-    import umnp.microcontroller.umock.machine as machine
-
-
-class timezone:
-    def __init__(self):
-        pass
-
-    @property
-    def utc(self):
-        return None
-
-
-class datetime:
-    def __init__(self):
-        self.rtc = machine.RTC()
-        self._time = int(round(time.time()))
-
-        date = self.rtc.datetime()
-        self.year, self.month, self.day, self.hour, self.minute, self.second, self.second_fraction = date
-
-    def _update(self):
-        date = self.rtc.datetime()
-        self._time = time.time()
-        self.year, self.month, self.day, self.hour, self.minute, self.second, self.second_fraction = date
-
-    @classmethod
-    def utcnow(cls):
-        return datetime()
-
-    def timestamp(self):
-        return self._time
-
-    @classmethod
-    def now(cls, *args):
-        return datetime()
-
-    def timezone(self):
-        return timezone()
diff --git a/umnp/protocol/compat/time.py b/umnp/protocol/compat/time.py
deleted file mode 100644
index f6d2c32f3368ab0cf33335bbb148d892b4418201..0000000000000000000000000000000000000000
--- a/umnp/protocol/compat/time.py
+++ /dev/null
@@ -1,7 +0,0 @@
-try:
-    import typing
-except ImportError:
-    pass
-
-
-
diff --git a/umnp/protocol/constants.py b/umnp/protocol/constants.py
index 446b7ea017b67f35c423dbd25583a46f5beac793..633f37bc80a6557db135c4a231b0864eee5e0ebc 100644
--- a/umnp/protocol/constants.py
+++ b/umnp/protocol/constants.py
@@ -15,12 +15,12 @@ MSG_BYTE_ORDER: typing.Literal["little", "big"] = "big"
 MSG_LEN_PROTOCOL_VERSION: int = 2
 
 # message type, see unmp.protocol.messagetype
-MSG_LEN_TYPE: int = 2
+MSG_LEN_MESSAGE_TYPE: int = 2
 
 # message timestamp (of sending), seconds since epoch for version 0
 MSG_LEN_TIMESTAMP: int = 4
 
-# sender unique id (e.g. serial number)
+# sender unique id (e.g. serial number or mac address)
 MSG_LEN_SENDER_ID: int = 6
 
 # sender type (e.g. model of measurement or control device attached to microcontroller)
diff --git a/umnp/protocol/message.py b/umnp/protocol/message.py
index 17330f8b8130d0cfe7268be930affccba4f1805a..bd879870c6602d4281a114b0f5bf22032ddb0430 100644
--- a/umnp/protocol/message.py
+++ b/umnp/protocol/message.py
@@ -65,6 +65,10 @@ class Message:
     def version(self) -> int:
         return self._header.version
 
+    @property
+    def sender_id(self) -> str:
+        return ":".join(f"{byte:02x}" for byte in self._sender_id)
+
     @property
     def data(self) -> bytes:
         return self._data
@@ -76,9 +80,7 @@ class Message:
             sender_device_id=self._sender_id,
         )
         payload = self.data
-        payload_length = len(payload).to_bytes(
-            length=MSG_LEN_PAYLOAD_SIZE, byteorder=MSG_BYTE_ORDER
-        )
+        payload_length = len(payload).to_bytes(MSG_LEN_PAYLOAD_SIZE, MSG_BYTE_ORDER)
         return header.encode() + payload_length + payload
 
     @classmethod
@@ -86,8 +88,17 @@ class Message:
         header = MessageHeader.decode(data)
         offset = header.encoded_size_bytes
         try:
-            payload_bytes = data[offset : offset + MSG_LEN_PAYLOAD_SIZE]
-            payload_length = int.from_bytes(payload_bytes, MSG_BYTE_ORDER)
+            # print("data:", data)
+            # print("len(data):", len(data))
+            # print("offset:", offset)
+
+            payload_len_bytes = data[offset : offset + MSG_LEN_PAYLOAD_SIZE]
+            payload_length = int.from_bytes(payload_len_bytes, MSG_BYTE_ORDER)
+            # print(f"{payload_len_bytes=}")
+            # print(f"{payload_length=}")
+            payload = data[offset + MSG_LEN_PAYLOAD_SIZE :]
+            # print(f"expected: {payload=}")
+            # raise OSError
         except (KeyError, ValueError):
             logging.error("Invalid message: could not extract payload length")
             return None
@@ -96,7 +107,7 @@ class Message:
         payload = data[offset:]
         if len(payload) != payload_length:
             logging.error(
-                "Invalid message: mismatch between specified and actual payload length"
+                f"Invalid message: mismatch between specified {payload_length} and actual payload length {len(payload)}."
             )
             return None
 
diff --git a/umnp/protocol/message_header.py b/umnp/protocol/message_header.py
index 2bed493c2e10f2f0c89214cbfb0c4d7f87f54806..e13a2f6b81879d1a2a96396924c9b2f26d2dab5b 100644
--- a/umnp/protocol/message_header.py
+++ b/umnp/protocol/message_header.py
@@ -3,7 +3,7 @@ from umnp.protocol.constants import (
     MSG_PROTOCOL_VERSION,
     MSG_LEN_PROTOCOL_VERSION,
     MSG_BYTE_ORDER,
-    MSG_LEN_TYPE,
+    MSG_LEN_MESSAGE_TYPE,
     MSG_LEN_TIMESTAMP,
     MSG_LEN_SENDER_ID,
     MSG_LEN_SENDER_TYPE,
@@ -51,7 +51,7 @@ class MessageHeader:
 
     @property
     def message_type_encoded(self) -> bytes:
-        return self.message_type.to_bytes(MSG_LEN_TYPE, MSG_BYTE_ORDER)
+        return self.message_type.to_bytes(MSG_LEN_MESSAGE_TYPE, MSG_BYTE_ORDER)
 
     @property
     def version(self) -> int:
@@ -77,9 +77,15 @@ class MessageHeader:
     def timestamp(self) -> TimeStamp:
         return self._timestamp
 
+    @property
+    def sender_device_id_encoded(self) -> bytes:
+        if len(self._sender_device_id) != 6:
+            raise ValueError("Device ID length != 6 bytes")
+        return self._sender_device_id
+
     def encode(self) -> bytes:
         version = self.version_encoded
-        sender_id = self._sender_device_id
+        sender_id = self.sender_device_id_encoded
         sender_type = self.sender_dev_type_encoded
         message_type = self.message_type_encoded
         timestamp = self.timestamp_encoded
@@ -91,22 +97,24 @@ class MessageHeader:
             MSG_LEN_PROTOCOL_VERSION
             + MSG_LEN_SENDER_ID
             + MSG_LEN_SENDER_TYPE
-            + MSG_LEN_TYPE
+            + MSG_LEN_MESSAGE_TYPE
             + MSG_LEN_TIMESTAMP
         )
 
     @classmethod
     def decode(cls, data: bytes):
         offset = 0
-
+        protocol_bytes = -1
         if not (isinstance(data, bytes)):
             logging.error("Invalid message header: not bytes")
             return None
         try:
             protocol_bytes = data[:MSG_LEN_PROTOCOL_VERSION]
             protocol_version = int.from_bytes(protocol_bytes, MSG_BYTE_ORDER)
+
             cls._version = protocol_version
         except (KeyError, ValueError):
+            logging.error(f"Protocol bytes: {protocol_bytes}")
             logging.error("Invalid message header: could not extract version")
             return None
         offset += MSG_LEN_PROTOCOL_VERSION
@@ -122,25 +130,28 @@ class MessageHeader:
             return None
 
         try:
-            message_type_bytes = data[offset : offset + MSG_LEN_TYPE]
+            message_type_bytes = data[offset : offset + MSG_LEN_MESSAGE_TYPE]
             message_type = int.from_bytes(message_type_bytes, MSG_BYTE_ORDER)
 
         except (KeyError, ValueError):
             logging.error("Invalid message payload: could not extract version")
             return None
 
-        offset += MSG_LEN_TYPE
+        offset += MSG_LEN_MESSAGE_TYPE
 
         if protocol_version < 0 or protocol_version > MSG_PROTOCOL_VERSION:
             err = f"Invalid protocol version {protocol_version}, outside of range [0, {MSG_PROTOCOL_VERSION}]"
             logging.error(err)
             return None
-
+        message_ts = None
         try:
             message_ts = data[offset : offset + MSG_LEN_TIMESTAMP]
-            timestamp = TimeStamp(int.from_bytes(message_ts, byteorder=MSG_BYTE_ORDER))
-        except (KeyError, ValueError):
-            logging.error("Invalid message timestamp: could not extract version")
+            message_ts_int = int.from_bytes(message_ts, byteorder=MSG_BYTE_ORDER)
+            timestamp = TimeStamp(message_ts_int)
+        except (KeyError, ValueError) as e:
+            logging.error(
+                f"Invalid message timestamp: could not extract timestamp: {e}"
+            )
             return None
 
         return cls(
diff --git a/umnp/protocol/umnp_time.py b/umnp/protocol/umnp_time.py
new file mode 100644
index 0000000000000000000000000000000000000000..47150b668e98eb60727884be7ca6bf85950b2869
--- /dev/null
+++ b/umnp/protocol/umnp_time.py
@@ -0,0 +1,5 @@
+import time
+
+
+def seconds_since_epoch() -> int:
+    return int(time.time())