Bladeren bron

fix setting binary sernsor values

subDesTagesMitExtraKaese 1 maand geleden
bovenliggende
commit
f1f14b6cd0
4 gewijzigde bestanden met toevoegingen van 59 en 28 verwijderingen
  1. 8 4
      main.py
  2. 30 20
      src/homeassistant.py
  3. 18 4
      src/protocol.py
  4. 3 0
      src/variables.py

+ 8 - 4
main.py

@@ -3,6 +3,7 @@
 import argparse
 import asyncio
 import signal
+import traceback
 
 import aiomqtt
 from bleak import BleakScanner
@@ -29,8 +30,10 @@ async def subscribe_and_watch_switches(sensor: MqttSensor, mppt: BleClient):
     variable_container = VariableContainer([variable])
     await sensor.subscribe(variable_container)
     await sensor.store_config(variable_container)
-    for command in await sensor.get_commands():
-        results = mppt.write([command])
+    while True:
+        command = await sensor.get_command()
+        print(f"Received command to set {command.name} to '{command.value}'")
+        results = await mppt.write([command])
         await sensor.publish(results)
 
 async def run_mppt(sensor: MqttSensor, address: str):
@@ -44,7 +47,8 @@ async def run_mppt(sensor: MqttSensor, address: str):
                 await asyncio.sleep(request_interval)
                 if not task.cancelled() and task.exception:
                     break
-
+    except asyncio.TimeoutError:
+        print("BLE communication timed out")
     except BleakDeviceNotFoundError:
         print(f"BLE device with address {address} was not found")
     except BleakError as e:
@@ -70,7 +74,7 @@ async def run_mqtt(address, host, port, username, password):
         except asyncio.CancelledError:
             raise  # Re-raise the CancelledError to stop the task
         except Exception as e:
-            print(f"An error occurred during BLE communication: {e}")
+            print(traceback.format_exc())
         await asyncio.sleep(reconnect_interval)
 
 async def main(*args):

+ 30 - 20
src/homeassistant.py

@@ -16,19 +16,20 @@ class MqttSensor(Client):
     # Define the device information
     device_info = {
         "identifiers": ["solarlife_mppt_ble"],
-        "name": "Solarlife MPPT",
+        "name": "Solarlife",
         "manufacturer": "Solarlife",
     }
 
     def __init__(self, *args, **kwargs):
-        super(MqttSensor, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.known_names = set()
+        self.subscribed_names = set()
 
     # https://www.home-assistant.io/integrations/#search/mqtt
     def get_platform(self, variable: Variable) -> str:
-        is_writable = FunctionCodes.WRITE_MEMORY_SINGLE in value.function_codes or \
-                      FunctionCodes.WRITE_STATUS_REGISTER in value.function_codes
-        is_numeric = value.multiplier != 0
+        is_writable = FunctionCodes.WRITE_MEMORY_SINGLE.value in variable.function_codes or \
+                      FunctionCodes.WRITE_STATUS_REGISTER.value in variable.function_codes
+        is_numeric = variable.multiplier != 0
         if variable.binary_payload:
             on, off = variable.binary_payload
             if is_writable and off:
@@ -62,11 +63,14 @@ class MqttSensor(Client):
             if key in self.known_names:
                 continue
             self.known_names.add(key)
-            print(f"publishing homeassistant config for {key}")
+
+            platform = self.get_platform(variable)
             config_topic = self.get_config_topic(variable)
             state_topic = self.get_state_topic(variable)
             command_topic = self.get_command_topic(variable)
 
+            print(f"Publishing homeassistant config for {platform} {key}")
+
             # Create the MQTT Discovery payload
             payload = {
                 "name": variable.friendly_name,
@@ -111,12 +115,12 @@ class MqttSensor(Client):
                 payload["payload_off"] = off
 
             # Handle writable entities
-            if FunctionCodes.WRITE_MEMORY_SINGLE in variable.function_codes or \
-               FunctionCodes.WRITE_STATUS_REGISTER in variable.function_codes:
+            if FunctionCodes.WRITE_MEMORY_SINGLE.value in variable.function_codes or \
+               FunctionCodes.WRITE_STATUS_REGISTER.value in variable.function_codes:
                 payload["command_topic"] = command_topic
 
             # Publish the MQTT Discovery payload
-            await self.publish(config_topic, payload=json.dumps(payload), retain=True)
+            await super().publish(config_topic, payload=json.dumps(payload), retain=True)
 
     async def publish(self, details: ResultContainer):
         # Publish each item in the details dictionary to its own MQTT topic
@@ -124,18 +128,24 @@ class MqttSensor(Client):
             state_topic = self.get_state_topic(value)
 
             # Publish the entity state
-            await super(MqttSensor, self).publish(state_topic, payload=str(value.value))
+            await super().publish(state_topic, payload=str(value.value))
 
     async def subscribe(self, variables: VariableContainer):
         for key, variable in variables.items():
-            if FunctionCodes.WRITE_MEMORY_SINGLE in value.function_codes or \
-               FunctionCodes.WRITE_STATUS_REGISTER in value.function_codes:
-                command_topic = self.get_command_topic(value)
-                await super(MqttSensor, self).subscribe(topic=command_topic, qos=2)
+            if FunctionCodes.WRITE_MEMORY_SINGLE.value in variable.function_codes or \
+               FunctionCodes.WRITE_STATUS_REGISTER.value in variable.function_codes:
+                if key in self.subscribed_names:
+                    continue
+                self.subscribed_names.add(key)
+                platform = self.get_platform(variable)
+                command_topic = self.get_command_topic(variable)
+                print(f"Subscribing to homeassistant commands for {platform} {variable.name}")
+                await super().subscribe(topic=command_topic, qos=2)
     
-    async def get_commands(self) -> ResultContainer:
-        for message in await self.messages:
-            match = re.match(rf"^{self.base_topic}/\w+/{self.sensor_name}/(\w+)/", message.topic)
-            variable_name = match.group(1)
-            variable = variables[variable_name]
-            yield Result(**vars(variable), value=message.payload)
+    async def get_command(self) -> Result:
+        message = await anext(self.messages)
+        match = re.match(rf"^{self.base_topic}/\w+/{self.sensor_name}/(\w+)/", message.topic.value)
+        variable_name = match.group(1)
+        variable = variables[variable_name]
+        value = str(message.payload, encoding="utf8")
+        return Result(**vars(variable), value=value)

+ 18 - 4
src/protocol.py

@@ -29,6 +29,9 @@ class ResultContainer:
 
     def __iter__(self):
         return iter(self._results)
+    
+    def __bool__(self):
+        return len(self._results) > 0
 
     def items(self):
         return self._result_map.items()
@@ -65,12 +68,18 @@ class LumiaxClient:
         return value
 
     def value_to_bytes(self, variable: Variable, buffer: bytearray, offset: int, value: Value) -> int:
-        if variable.multiplier:
+        if variable.multiplier and not variable.func:
             raw_value = round(float(value) * variable.multiplier)
         elif variable.func:
             raw_value = self._find_raw_value_by_brute_force(variable, value)
             if raw_value == None:
                 raise Exception(f"invalid value for {variable.name}: '{value}'")
+        elif variable.binary_payload and value == variable.binary_payload[0]:
+            raw_value = 1
+        elif variable.binary_payload and value == variable.binary_payload[1]:
+            raw_value = 0
+        elif variable.binary_payload:
+            raise Exception(f"invalid binary value for {variable.name}: '{value}'")
         else:
             raw_value = int(value)
 
@@ -182,7 +191,7 @@ class LumiaxClient:
             received_crc = buffer[3+data_length:3+data_length+2]
             calculated_crc = crc16(buffer[:3+data_length])
             if received_crc != calculated_crc:
-                raise Exception(f"CRC mismatch ({calculated_crc} != {received_crc})")
+                raise Exception(f"CRC mismatch (0x{calculated_crc.hex()} != 0x{received_crc.hex()})")
 
             address = start_address
             cursor = 3
@@ -200,13 +209,18 @@ class LumiaxClient:
             received_crc = buffer[6:8]
             calculated_crc = crc16(buffer[:6])
             if received_crc != calculated_crc:
-                raise Exception(f"CRC mismatch ({calculated_crc} != {received_crc})")
+                raise Exception(f"CRC mismatch (0x{calculated_crc.hex()} != 0x{received_crc.hex()})")
+            
+            if function_code in [FunctionCodes.WRITE_MEMORY_SINGLE, FunctionCodes.WRITE_STATUS_REGISTER]:
+                variable = [v for v in variables if address == v.address and function_code.value in v.function_codes][0]
+                value = self.bytes_to_value(variable, buffer, 4)
+                results.append(Result(**vars(variable), value=value))
 
         return ResultContainer(results)
 
     def _find_raw_value_by_brute_force(self, variable: Variable, value: str):
         if variable.multiplier:
-            value = float(value)
+            value = float(value) * variable.multiplier
         n_bits = 32 if variable.is_32_bit else 16
         if variable.is_signed:
             for i in range(0, 2**(n_bits-1) + 1):

+ 3 - 0
src/variables.py

@@ -45,6 +45,9 @@ class VariableContainer:
     def __iter__(self):
         return iter(self._variables)
 
+    def __bool__(self):
+        return len(self._variables) > 0
+
     def items(self):
         return self._variable_map.items()