Repository: TomerFi/aioswitcher

Issue:
Incorrect parsing of MAC address for Switcher Runner
### What happened?

Details in https://github.com/home-assistant/core/issues/122715#issuecomment-2254539132, it looks like we have a one byte shift when parsing the MAC address for Switcher Runner.
@YogevBokobza you recently checked all runner devices, can you verify that?


### Module Version

3.4.3

### Device Type

Switcher Runner

### Firmware Version

1.79

### Relevant log output

```shell
39:E8:68:E7:80:3B
3C:E8:68:E7:80:3B
3F:E8:68:E7:80:3B
49:E8:68:E7:80:3B
```
The real device MAC starts with `E8` so the first byte is not related to the MAC
```


Discussion:
YogevBokobza: @thecode 
Will this fix work for HA? how does it retrieve the MAC there?
YogevBokobza: @thecode 
I just wonder how we didn't see this in HA until now.. it looks like an old issue..
thecode: > @thecode I just wonder how we didn't see this in HA until now.. it looks like an old issue..

We did, there were few reports of it, the shift itself is not creating a problem as long as the left bits are not changing so most people probably doesn't notice it.

I looked at the message history, this was reported to Switcher on the 30/11/2022, they said they will check and report back (still waiting...)
TomerFi: @YogevBokobza - Thank you!

Patch:
diff --git a/src/aioswitcher/bridge.py b/src/aioswitcher/bridge.py
index 25ae2f3..1676123 100644
--- a/src/aioswitcher/bridge.py
+++ b/src/aioswitcher/bridge.py
@@ -104,7 +104,7 @@ def _parse_device_from_datagram(
                     parser.get_device_id(),
                     parser.get_device_key(),
                     parser.get_ip_type1(),
-                    parser.get_mac(),
+                    parser.get_mac_type1(),
                     parser.get_name(),
                     device_type.token_needed,
                     power_consumption,
@@ -127,7 +127,7 @@ def _parse_device_from_datagram(
                     parser.get_device_id(),
                     parser.get_device_key(),
                     parser.get_ip_type1(),
-                    parser.get_mac(),
+                    parser.get_mac_type1(),
                     parser.get_name(),
                     device_type.token_needed,
                     power_consumption,
@@ -144,7 +144,7 @@ def _parse_device_from_datagram(
                     parser.get_device_id(),
                     parser.get_device_key(),
                     parser.get_ip_type2(),
-                    parser.get_mac(),
+                    parser.get_mac_type2(),
                     parser.get_name(),
                     device_type.token_needed,
                     parser.get_shutter_position(
@@ -168,7 +168,7 @@ def _parse_device_from_datagram(
                     parser.get_device_id(),
                     parser.get_device_key(),
                     parser.get_ip_type2(),
-                    parser.get_mac(),
+                    parser.get_mac_type2(),
                     parser.get_name(),
                     device_type.token_needed,
                     parser.get_shutter_position(
@@ -197,7 +197,7 @@ def _parse_device_from_datagram(
                     parser.get_device_id(),
                     parser.get_device_key(),
                     parser.get_ip_type2(),
-                    parser.get_mac(),
+                    parser.get_mac_type2(),
                     parser.get_name(),
                     device_type.token_needed,
                     parser.get_thermostat_mode(),
@@ -351,8 +351,8 @@ def get_ip_type2(self) -> str:
         ip_addr = int(hex_ip[0:2] + hex_ip[2:4] + hex_ip[4:6] + hex_ip[6:8], 16)
         return inet_ntoa(pack(">L", ip_addr))
 
-    def get_mac(self) -> str:
-        """Extract the MAC address from the broadcast message."""
+    def get_mac_type1(self) -> str:
+        """Extract the MAC address from the broadcast message (Heater, Plug)."""
         hex_mac = hexlify(self.message)[160:172].decode().upper()
         return (
             hex_mac[0:2]
@@ -368,6 +368,23 @@ def get_mac(self) -> str:
             + hex_mac[10:12]
         )
 
+    def get_mac_type2(self) -> str:
+        """Extract the MAC address from the broadcast message (Breeze, Runners)."""
+        hex_mac = hexlify(self.message)[162:174].decode().upper()
+        return (
+            hex_mac[0:2]
+            + ":"
+            + hex_mac[2:4]
+            + ":"
+            + hex_mac[4:6]
+            + ":"
+            + hex_mac[6:8]
+            + ":"
+            + hex_mac[8:10]
+            + ":"
+            + hex_mac[10:12]
+        )
+
     def get_name(self) -> str:
         """Extract the device name from the broadcast message."""
         return self.message[42:74].decode().rstrip("\x00")


Test patch:
diff --git a/tests/test_udp_datagram_parsing.py b/tests/test_udp_datagram_parsing.py
index 326532c..22a19a7 100644
--- a/tests/test_udp_datagram_parsing.py
+++ b/tests/test_udp_datagram_parsing.py
@@ -39,7 +39,7 @@ def test_datagram_state_off(resource_path, type_suffix, expected_type):
 
     assert_that(sut_parser.is_switcher_originator()).is_true()
     assert_that(sut_parser.get_ip_type1()).is_equal_to("192.168.1.33")
-    assert_that(sut_parser.get_mac()).is_equal_to("12:A1:A2:1A:BC:1A")
+    assert_that(sut_parser.get_mac_type1()).is_equal_to("12:A1:A2:1A:BC:1A")
     assert_that(sut_parser.get_name()).is_equal_to("My Switcher Boiler")
     assert_that(sut_parser.get_device_id()).is_equal_to("aaaaaa")
     assert_that(sut_parser.get_device_state()).is_equal_to(DeviceState.OFF)
@@ -65,7 +65,7 @@ def test_datagram_state_on(resource_path, type_suffix, expected_type):
 
     assert_that(sut_parser.is_switcher_originator()).is_true()
     assert_that(sut_parser.get_ip_type1()).is_equal_to("192.168.1.33")
-    assert_that(sut_parser.get_mac()).is_equal_to("12:A1:A2:1A:BC:1A")
+    assert_that(sut_parser.get_mac_type1()).is_equal_to("12:A1:A2:1A:BC:1A")
     assert_that(sut_parser.get_name()).is_equal_to("My Switcher Boiler")
     assert_that(sut_parser.get_device_id()).is_equal_to("aaaaaa")
     assert_that(sut_parser.get_device_state()).is_equal_to(DeviceState.ON)