Repository: mill1000/midea-msmart (python)

Issue:
Improve temperature sensor resolution
Some units are capable of reporting additional temperature sensor precision in byte 15 of the StateResponse message.

e.g.
```
Byte                       11 12 13 14 15 16
c00188647f7f00 00 00 00 00 63 45 0c 00 56 19 00000000000002
```

The indoor temperature is the lower nibble, outdoor temperature the upper nibble.

Temperature is calculated as `T = Base + X/10` where X is the value in the associated nibble

Pull request:
Improve reported temperature precision
Close #92 

Patch:
diff --git a/msmart/device/AC/command.py b/msmart/device/AC/command.py
index b9e8df3..5a4d46e 100644
--- a/msmart/device/AC/command.py
+++ b/msmart/device/AC/command.py
@@ -618,6 +618,13 @@ class StateResponse(Response):
 
         self.display_on = (payload[14] != 0x70)
 
+        # Decode additional temperature resolution
+        if self.indoor_temperature:
+            self.indoor_temperature += (payload[15] & 0xF) / 10
+
+        if self.outdoor_temperature:
+            self.outdoor_temperature += (payload[15] >> 4) / 10
+
         # TODO dudanov/MideaUART humidity set point in byte 19, mask 0x7F
 
         # TODO Some payloads are shorter than expected. Unsure what, when or why


Test patch:
diff --git a/msmart/device/AC/test_command.py b/msmart/device/AC/test_command.py
index 9ffcec3..160ac3c 100644
--- a/msmart/device/AC/test_command.py
+++ b/msmart/device/AC/test_command.py
@@ -42,6 +42,7 @@ class TestStateResponse(_TestResponseBase):
         return cast(StateResponse, resp)
 
     def test_message_checksum(self) -> None:
+        # https://github.com/mill1000/midea-ac-py/issues/11#issuecomment-1650647625
         # V3 state response with checksum as CRC, and shorter than expected
         TEST_MESSAGE_CHECKSUM_AS_CRC = bytes.fromhex(
             "aa1eac00000000000003c0004b1e7f7f000000000069630000000000000d33")
@@ -89,6 +90,70 @@ class TestStateResponse(_TestResponseBase):
         self.assertEqual(resp.indoor_temperature, 21.0)
         self.assertEqual(resp.outdoor_temperature, 28.5)
 
+    def test_message_additional_precision(self) -> None:
+        """Test decoding of temperatures with higher precision."""
+        # Messages with additional temperature precision bits
+        TEST_MESSAGES = {
+            # https://github.com/mill1000/midea-msmart/issues/89#issuecomment-1783316836
+            (24.0, 25.1, 10.0): bytes.fromhex(
+                "aa23ac00000000000203c00188647f7f000000000063450c0056190000000000000497c3"),
+            # https://github.com/mill1000/midea-msmart/issues/89#issuecomment-1782352164
+            (24.0, 27.0, 10.2): bytes.fromhex(
+                "aa23ac00000000000203c00188647f7f000000000067450c00750000000000000001a3b0"),
+            (24.0, 25.0, 10.0): bytes.fromhex(
+                "aa23ac00000000000203c00188647f7f000080000064450c00501d00000000000001508e"),
+        }
+
+        for targets, message in TEST_MESSAGES.items():
+            # Create response from the message
+            resp = self._test_response(message)
+
+            # Assert response is a state response
+            self.assertEqual(type(resp), StateResponse)
+
+            # Suppress type errors
+            resp = cast(StateResponse, resp)
+
+            target, indoor, outdoor = targets
+
+            self.assertEqual(resp.target_temperature, target)
+            self.assertEqual(resp.indoor_temperature, indoor)
+            self.assertEqual(resp.outdoor_temperature, outdoor)
+
+        # Raw responses with additional temperature precision bits
+        TEST_RESPONSES = {
+            # https://github.com/mill1000/midea-ac-py/issues/39#issuecomment-1729884851
+            # Corrected target values from user reported values
+            (16.0, 23.2, 18.4): bytes.fromhex("c00181667f7f003c00000060560400420000000000000048"),
+            (16.5, 23.4, 18.4): bytes.fromhex("c00191667f7f003c00000060560400440000000000000049"),
+            (17.0, 24.1, 18.3): bytes.fromhex("c00181667f7f003c0000006156050036000000000000004a"),
+            (17.5, 24.3, 18.2): bytes.fromhex("c00191667f7f003c0000006156050028000000000000004b"),
+            (18.0, 24.3, 18.2): bytes.fromhex("c00182667f7f003c0000006156060028000000000000004c"),
+            (18.5, 24.3, 18.2): bytes.fromhex("c00192667f7f003c0000006156060028000000000000004d"),
+            (19.0, 24.3, 18.2): bytes.fromhex("c00183667f7f003c0000006156070028000000000000004e"),
+            (19.5, 24.0, 19.0): bytes.fromhex("c00193667f7f003c00000061570700550000000000000050"),
+        }
+
+        for targets, payload in TEST_RESPONSES.items():
+            # Create response
+            with memoryview(payload) as mv_payload:
+                resp = StateResponse(mv_payload)
+
+            # Assert that it exists
+            self.assertIsNotNone(resp)
+
+            # Assert response is a state response
+            self.assertEqual(type(resp), StateResponse)
+
+            # Suppress type errors
+            resp = cast(StateResponse, resp)
+
+            target, indoor, outdoor = targets
+
+            self.assertEqual(resp.target_temperature, target)
+            self.assertEqual(resp.indoor_temperature, indoor)
+            self.assertEqual(resp.outdoor_temperature, outdoor)
+
     def test_target_temperature(self) -> None:
         """Test decoding of target temperature from a variety of state responses."""
         TEST_PAYLOADS = {