Repository: Bluetooth-Devices/govee-ble (language: python)

Issue:
H5051 in little endian
**Describe the bug**
Shouldn't this be in little endian?

https://github.com/Bluetooth-Devices/govee-ble/blob/432223ada34def7bb58a1601afdfd6954cc3a152/src/govee_ble/parser.py#L190

**Additional context**
HCI dump from https://github.com/custom-components/ble_monitor/issues/467

```
> 04 3E 19 02 01 04 00 92 E4 21 59 60 E3 0D 0C FF 88 EC 00 BA 
  0A F5 0F 64 C2 01 01 C5
```

Will use this code (no local name)

```python
        if msg_length == 9 and (
            "H5051" in local_name or "H5071" in local_name or mgr_id == 0xEC88
        ):
            self.set_device_type("H5051/H5071")
            (temp, humi, batt) = PACKED_hHB.unpack(data[1:6])
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
            )
            self.update_predefined_sensor(
                SensorLibrary.HUMIDITY__PERCENTAGE, humi / 100
            )
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return
```

In little endian, this makes sense. 

`BA 0A` --> 2746 / 100 = 27.46 deg
`F5 0F` --> 4085 / 100 = 40.85 %
 `64` --> 100%

However, `PACKED_hHB.unpack(data[1:6])` is using big endian `PACKED_hHB = struct.Struct(">hHB")`


Discussion / hints:
https://github.com/Bluetooth-Devices/govee-ble/pull/34

Need to compare with this but mobile so can't dig right now 
I think we need to remove 5071 from that block as it's actually being parsed right above it 
Yes, I think we can remove the H5071 from the 2nd block, but I think we can move the H5051 to the first block as well, as the H5051 is in little endian as well. The only difference between the first and 2nd block is little vs big endian. 
The HCI dump above is from a H5051, and it uses little endian (1st block). So, there is no device that used big endian (2nd block). 

So, I think we should delete the 2nd block and change the first block to 

```python
        if msg_length == 9 and (
            mgr_id == 0xEC88
            or "H5051" in local_name  # just to be sure
            or "H5052" in local_name
            or "H5071" in local_name
        ):
            if "H5051" in local_name:
                self.set_device_type("H5051")
            elif "H5052" in local_name:
                self.set_device_type("H5052")
            elif "H5071" in local_name:
                self.set_device_type("H5071")
            else:
                self.set_device_type("H5051")  # I think if it hasn't a name, like in my HCI dump, it is a H5051
            (temp, humi, batt) = PACKED_hHB_LITTLE.unpack(data[1:6])
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
            )
            self.update_predefined_sensor(
                SensorLibrary.HUMIDITY__PERCENTAGE, humi / 100
            )
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return
```

Sounds good.  Mind doing a PR?
Yes, sure, doing a final check in BLE monitor first. 

I'll make a BLE monitor release first, and see if it all works out. If no-one complains, I'll create the PR. 

I also have info about the H5198, another 4-probe meat sensor. If the user is confirming it works, I will add that one in the same PR. Wil probably do that at the end of the week, such that we know it works OK in BLE monitor. 

https://github.com/custom-components/ble_monitor/pull/1091/
Thanks @Ernst79 

Fix patch:
diff --git a/src/govee_ble/parser.py b/src/govee_ble/parser.py
index 1c27839..372447c 100644
--- a/src/govee_ble/parser.py
+++ b/src/govee_ble/parser.py
@@ -34,6 +34,13 @@
 
 NOT_GOVEE_MANUFACTURER = {76}
 
+PROBE_MAPPING_1_2 = [1, 2]
+PROBE_MAPPING_3_4 = [3, 4]
+
+FOUR_PROBES_MAPPING = {
+    sensor_id: PROBE_MAPPING_1_2 for sensor_id in (0x01, 0x41, 0x81, 0xC1)
+} | {sensor_id: PROBE_MAPPING_3_4 for sensor_id in (0x02, 0x42, 0x82, 0xC2)}
+
 
 def decode_temp_humid(temp_humid_bytes: bytes) -> tuple[float, float]:
     """Decode potential negative temperatures."""
@@ -78,7 +85,6 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None:
 
         if local_name.startswith("GV"):
             self.set_device_name(service_info.name[2:].replace("_", " "))
-
         self.set_precision(2)
 
         for mfr_id, mfr_data in manufacturer_data.items():
@@ -164,15 +170,19 @@ def _process_mfr_data(
             self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
             return
 
-        if (
-            msg_length == 9
-            and mgr_id == 0xEC88
-            and ("H5071" in local_name or "H5052" in local_name)
+        if msg_length == 9 and (
+            mgr_id == 0xEC88
+            or "H5051" in local_name
+            or "H5052" in local_name
+            or "H5071" in local_name
         ):
             if "H5071" in local_name:
                 self.set_device_type("H5071")
-            else:
+            elif "H5052" in local_name:
                 self.set_device_type("H5052")
+            else:
+                self.set_device_type("H5051")
+                self.set_device_name(f"H5051 {short_address(address)}")
             (temp, humi, batt) = PACKED_hHB_LITTLE.unpack(data[1:6])
             self.update_predefined_sensor(
                 SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
@@ -183,20 +193,6 @@ def _process_mfr_data(
             self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
             return
 
-        if msg_length == 9 and (
-            "H5051" in local_name or "H5071" in local_name or mgr_id == 0xEC88
-        ):
-            self.set_device_type("H5051/H5071")
-            (temp, humi, batt) = PACKED_hHB.unpack(data[1:6])
-            self.update_predefined_sensor(
-                SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
-            )
-            self.update_predefined_sensor(
-                SensorLibrary.HUMIDITY__PERCENTAGE, humi / 100
-            )
-            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
-            return
-
         if msg_length == 9 and (
             "H5178" in local_name or "B5178" in local_name or mgr_id == 0x0001
         ):
@@ -328,11 +324,7 @@ def _process_mfr_data(
                 temp_probe_second,
                 temp_alarm_second,
             ) = PACKED_hhbhh.unpack(data[8:17])
-            if sensor_id == 1:
-                ids = [1, 2]
-            elif sensor_id == 2:
-                ids = [3, 4]
-            else:
+            if not (ids := FOUR_PROBES_MAPPING.get(sensor_id)):
                 if debug_logging:
                     _LOGGER.debug(
                         "Unknown sensor id: %s for a H5184, data: %s",
@@ -374,6 +366,41 @@ def _process_mfr_data(
             )
             return
 
+        if msg_length == 20 and (
+            "H5198" in local_name
+            or mgr_id == 0x3022
+            or "00009851-0000-1000-8000-00805f9b34fb" in service_uuids
+        ):
+            sensor_id = data[6]
+            self.set_device_type("H5198")
+            self.set_device_name(f"H5198 {short_address(address)}")
+            (
+                temp_probe_first,
+                temp_alarm_first,
+                _,
+                temp_probe_second,
+                temp_alarm_second,
+            ) = PACKED_hhhhh.unpack(data[8:18])
+            if not (ids := FOUR_PROBES_MAPPING.get(sensor_id)):
+                if debug_logging:
+                    _LOGGER.debug(
+                        "Unknown sensor id: %s for a H5198, data: %s",
+                        sensor_id,
+                        hex(data),
+                    )
+                return
+            self.update_temp_probe_with_alarm(
+                decode_temps_probes(temp_probe_first),
+                decode_temps_probes(temp_alarm_first),
+                ids[0],
+            )
+            self.update_temp_probe_with_alarm(
+                decode_temps_probes(temp_probe_second),
+                decode_temps_probes(temp_alarm_second),
+                ids[1],
+            )
+            return
+
     def update_temp_probe(self, temp: float, probe_id: int) -> None:
         """Update the temperature probe with the alarm temperature."""
         self.update_predefined_sensor(


Test patch:
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 3061de6..07f54ca 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -12,6 +12,15 @@
 
 from govee_ble.parser import GoveeBluetoothDeviceData
 
+GVH5051_SERVICE_INFO = BluetoothServiceInfo(
+    name="",
+    address="61DE521B-F0BF-9F44-64D4-75BBE1738105",
+    rssi=-63,
+    manufacturer_data={60552: b"\x00\xba\x0a\xf9\x0f\x63\x02\x01\x01"},
+    service_uuids=["0000ec88-0000-1000-8000-00805f9b34fb"],
+    service_data={},
+    source="local",
+)
 GVH5075_SERVICE_INFO = BluetoothServiceInfo(
     name="GVH5075_2762",
     address="61DE521B-F0BF-9F44-64D4-75BBE1738105",
@@ -117,6 +126,39 @@
     service_data={},
     source="local",
 )
+GVH5198_SERVICE_INFO = BluetoothServiceInfo(
+    name="",
+    address="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
+    rssi=-56,
+    manufacturer_data={
+        12322: b"\x6c\x01\x00\x01\x01\xa4\xc2\x0f\x0e\x10\xff\xff\xff\xff\x08\xfc\xff\xff\xff\xff"
+    },
+    service_uuids=["00009851-0000-1000-8000-00805f9b34fb"],
+    service_data={},
+    source="local",
+)
+GVH5198_VARIANT_SERVICE_INFO = BluetoothServiceInfo(
+    name="",
+    address="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
+    rssi=-56,
+    manufacturer_data={
+        12322: b"\x6c\x01\x00\x01\x01\xa3\xc1\x0f\x0b\xb8\xff\xff\xff\xff\t`\xff\xff\xff\xff"
+    },
+    service_uuids=["00009851-0000-1000-8000-00805f9b34fb"],
+    service_data={},
+    source="local",
+)
+GVH5198_INVALID_SERVICE_INFO = BluetoothServiceInfo(
+    name="",
+    address="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
+    rssi=-56,
+    manufacturer_data={
+        12322: b"\x6c\x01\x00\x01\x01\xa3\xd4\x0f\x0b\xb8\xff\xff\xff\xff\t`\xff\xff\xff\xff"
+    },
+    service_uuids=["00009851-0000-1000-8000-00805f9b34fb"],
+    service_data={},
+    source="local",
+)
 GVH5185_SERVICE_INFO = BluetoothServiceInfo(
     name="GVH5185_2EC8",
     address="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
@@ -342,6 +384,68 @@ def test_can_create():
     GoveeBluetoothDeviceData()
 
 
+def test_gvh5051():
+    parser = GoveeBluetoothDeviceData()
+    service_info = GVH5051_SERVICE_INFO
+    result = parser.update(service_info)
+    assert result == SensorUpdate(
+        title=None,
+        devices={
+            None: SensorDeviceInfo(
+                name="H5051 8105",
+                model="H5051",
+                manufacturer="Govee",
+                sw_version=None,
+                hw_version=None,
+            )
+        },
+        entity_descriptions={
+            DeviceKey(key="temperature", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="temperature", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(key="humidity", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="humidity", device_id=None),
+                device_class=DeviceClass.HUMIDITY,
+                native_unit_of_measurement=Units.PERCENTAGE,
+            ),
+            DeviceKey(key="battery", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="battery", device_id=None),
+                device_class=DeviceClass.BATTERY,
+                native_unit_of_measurement=Units.PERCENTAGE,
+            ),
+            DeviceKey(key="signal_strength", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                device_class=DeviceClass.SIGNAL_STRENGTH,
+                native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
+            ),
+        },
+        entity_values={
+            DeviceKey(key="temperature", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature", device_id=None),
+                name="Temperature",
+                native_value=27.46,
+            ),
+            DeviceKey(key="humidity", device_id=None): SensorValue(
+                device_key=DeviceKey(key="humidity", device_id=None),
+                name="Humidity",
+                native_value=40.89,
+            ),
+            DeviceKey(key="battery", device_id=None): SensorValue(
+                device_key=DeviceKey(key="battery", device_id=None),
+                name="Battery",
+                native_value=99,
+            ),
+            DeviceKey(key="signal_strength", device_id=None): SensorValue(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                name="Signal Strength",
+                native_value=-63,
+            ),
+        },
+    )
+
+
 def test_gvh5052():
     parser = GoveeBluetoothDeviceData()
     service_info = GVH5052_SERVICE_INFO
@@ -1988,6 +2092,190 @@ def test_gvh5178_error():
     )
 
 
+def test_gvh5198_probe_1_2():
+    parser = GoveeBluetoothDeviceData()
+    service_info = GVH5198_VARIANT_SERVICE_INFO
+    result = parser.update(service_info)
+    assert result == SensorUpdate(
+        title=None,
+        devices={
+            None: SensorDeviceInfo(
+                name="H5198 AC3D",
+                model="H5198",
+                manufacturer="Govee",
+                sw_version=None,
+                hw_version=None,
+            )
+        },
+        entity_descriptions={
+            DeviceKey(key="temperature_probe_1", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="temperature_probe_1", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(
+                key="temperature_alarm_probe_1", device_id=None
+            ): SensorDescription(
+                device_key=DeviceKey(key="temperature_alarm_probe_1", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(key="temperature_probe_2", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="temperature_probe_2", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(
+                key="temperature_alarm_probe_2", device_id=None
+            ): SensorDescription(
+                device_key=DeviceKey(key="temperature_alarm_probe_2", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(key="signal_strength", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                device_class=DeviceClass.SIGNAL_STRENGTH,
+                native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
+            ),
+        },
+        entity_values={
+            DeviceKey(key="temperature_probe_1", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_probe_1", device_id=None),
+                name="Temperature Probe 1",
+                native_value=30.0,
+            ),
+            DeviceKey(key="temperature_alarm_probe_1", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_alarm_probe_1", device_id=None),
+                name="Temperature Alarm Probe 1",
+                native_value=0.0,
+            ),
+            DeviceKey(key="temperature_probe_2", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_probe_2", device_id=None),
+                name="Temperature Probe 2",
+                native_value=24.0,
+            ),
+            DeviceKey(key="temperature_alarm_probe_2", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_alarm_probe_2", device_id=None),
+                name="Temperature Alarm Probe 2",
+                native_value=0.0,
+            ),
+            DeviceKey(key="signal_strength", device_id=None): SensorValue(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                name="Signal Strength",
+                native_value=-56,
+            ),
+        },
+    )
+
+
+def test_gvh5198_probe_3_4():
+    parser = GoveeBluetoothDeviceData()
+    service_info = GVH5198_SERVICE_INFO
+    result = parser.update(service_info)
+    assert result == SensorUpdate(
+        title=None,
+        devices={
+            None: SensorDeviceInfo(
+                name="H5198 AC3D",
+                model="H5198",
+                manufacturer="Govee",
+                sw_version=None,
+                hw_version=None,
+            )
+        },
+        entity_descriptions={
+            DeviceKey(key="temperature_probe_3", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="temperature_probe_3", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(
+                key="temperature_alarm_probe_3", device_id=None
+            ): SensorDescription(
+                device_key=DeviceKey(key="temperature_alarm_probe_3", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(key="temperature_probe_4", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="temperature_probe_4", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(
+                key="temperature_alarm_probe_4", device_id=None
+            ): SensorDescription(
+                device_key=DeviceKey(key="temperature_alarm_probe_4", device_id=None),
+                device_class=DeviceClass.TEMPERATURE,
+                native_unit_of_measurement=Units.TEMP_CELSIUS,
+            ),
+            DeviceKey(key="signal_strength", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                device_class=DeviceClass.SIGNAL_STRENGTH,
+                native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
+            ),
+        },
+        entity_values={
+            DeviceKey(key="temperature_probe_3", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_probe_3", device_id=None),
+                name="Temperature Probe 3",
+                native_value=36.0,
+            ),
+            DeviceKey(key="temperature_alarm_probe_3", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_alarm_probe_3", device_id=None),
+                name="Temperature Alarm Probe 3",
+                native_value=0.0,
+            ),
+            DeviceKey(key="temperature_probe_4", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_probe_4", device_id=None),
+                name="Temperature Probe 4",
+                native_value=23.0,
+            ),
+            DeviceKey(key="temperature_alarm_probe_4", device_id=None): SensorValue(
+                device_key=DeviceKey(key="temperature_alarm_probe_4", device_id=None),
+                name="Temperature Alarm Probe 4",
+                native_value=0.0,
+            ),
+            DeviceKey(key="signal_strength", device_id=None): SensorValue(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                name="Signal Strength",
+                native_value=-56,
+            ),
+        },
+    )
+
+
+def test_gvh5198_invalid():
+    parser = GoveeBluetoothDeviceData()
+    service_info = GVH5198_INVALID_SERVICE_INFO
+    result = parser.update(service_info)
+    assert result == SensorUpdate(
+        title=None,
+        devices={
+            None: SensorDeviceInfo(
+                name="H5198 AC3D",
+                model="H5198",
+                manufacturer="Govee",
+                sw_version=None,
+                hw_version=None,
+            )
+        },
+        entity_descriptions={
+            DeviceKey(key="signal_strength", device_id=None): SensorDescription(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                device_class=DeviceClass.SIGNAL_STRENGTH,
+                native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
+            ),
+        },
+        entity_values={
+            DeviceKey(key="signal_strength", device_id=None): SensorValue(
+                device_key=DeviceKey(key="signal_strength", device_id=None),
+                name="Signal Strength",
+                native_value=-56,
+            ),
+        },
+    )
+
+
 def test_gvh5075_debug_hex(caplog):
     parser = GoveeBluetoothDeviceData()
     service_info = GVH5075_SERVICE_INFO