diff --git a/.gitignore b/.gitignore index 43f7617..9c76ebc 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,5 @@ ENV/ # mypy .mypy_cache/ + +config_*.json \ No newline at end of file diff --git a/aircon/__main__.py b/aircon/__main__.py index 2d0d0e2..0831c98 100644 --- a/aircon/__main__.py +++ b/aircon/__main__.py @@ -243,6 +243,9 @@ async def run(parsed_args): topics['temp']) config['max_temp'] = '86' if device.is_fahrenheit else '30' config['min_temp'] = '61' if device.is_fahrenheit else '16' + if 'display_temperature' in topics: + config['display_temperature_state_topic'] = mqtt_topics['pub'].format(device.mac_address, + topics['display_temperature']) mqtt_client.publish(mqtt_topics['discovery'].format(device.mac_address), payload=json.dumps(config), retain=True) diff --git a/aircon/aircon.py b/aircon/aircon.py index 855c825..48d5712 100644 --- a/aircon/aircon.py +++ b/aircon/aircon.py @@ -111,6 +111,9 @@ def get_property(self, name: str): def get_property_type(self, name: str): return self._properties.get_type(name) + def parse_property(self, name: str, value): + return self._properties.parse_attr(name, value) + def update_property(self, name: str, value, notify_value=None) -> None: """Update the stored properties, if changed.""" # Update value precision for value sent from the A/C @@ -123,11 +126,13 @@ def update_property(self, name: str, value, notify_value=None) -> None: with self._properties_lock: old_value = getattr(self._properties, name) + logging.debug(f"Updating {self}.{name} to {value}") if value != old_value: setattr(self._properties, name, value) # logging.debug('Updated properties: %s' % self._properties) if name == 't_control_value': self._update_controlled_properties(value) + logging.debug(f"Updated {self}.{name} to {getattr(self._properties, name)}") self._notify_listeners(name, notify_value) def _update_controlled_properties(self, control: int): @@ -546,7 +551,8 @@ def __init__(self, config: Dict[str, str], notifier: Callable[[None], None]): 'fan_speed': 'fan_speed', 'work_mode': 'operation_mode', 'swing_mode': 'af_vertical_swing', - 'temp': 'adjust_temperature' + 'temp': 'adjust_temperature', + 'display_temperature': 'display_temperature', } self.work_modes = ['off', 'fan_only', 'heat', 'cool', 'dry', 'auto'] self.fan_modes = ['auto', 'quiet', 'low', 'medium', 'high'] @@ -559,7 +565,8 @@ def __init__(self, config: Dict[str, str], notifier: Callable[[None], None]): self.topics = { 'fan_speed': 'fan_speed', 'work_mode': 'operation_mode', - 'temp': 'adjust_temperature' + 'temp': 'adjust_temperature', + 'display_temperature': 'display_temperature', } self.work_modes = ['off', 'fan_only', 'heat', 'cool', 'dry', 'auto'] self.fan_modes = ['auto', 'quiet', 'low', 'medium', 'high'] diff --git a/aircon/mqtt_client.py b/aircon/mqtt_client.py index 0e74ff7..5fd9a9d 100644 --- a/aircon/mqtt_client.py +++ b/aircon/mqtt_client.py @@ -19,8 +19,10 @@ def __init__(self, client_id: str, mqtt_topics: dict, devices: [Device]): def mqtt_on_connect(self, client: mqtt.Client, userdata, flags, rc): for device in self._devices: - client.subscribe([(self._mqtt_topics['sub'].format(device.mac_address, data_field.name), 0) - for data_field in fields(device.get_all_properties())]) + topics_fmt = [(self._mqtt_topics['sub'].format(device.mac_address, data_field.name), 0) + for data_field in fields(device.get_all_properties())] + logging.debug(f"Subscribing to topics{topics_fmt} for device {device}") + client.subscribe(topics_fmt) # Subscribe to subscription updates. client.subscribe('$SYS/broker/log/M/subscribe/#') diff --git a/aircon/properties.py b/aircon/properties.py index 0881205..39dbb7d 100644 --- a/aircon/properties.py +++ b/aircon/properties.py @@ -147,6 +147,20 @@ def _get_metadata(cls, attr: str): def get_type(cls, attr: str): return cls.__dataclass_fields__[attr].type + @classmethod + def parse_attr(cls, attr, value): + """If a field supplies a parser function in its metadata, use it to parse its value from the raw data.""" + # Retrieve the desired type from the class attribute type hinting + native_type = cls.__dataclass_fields__[attr].type + value_fmt = native_type(value) + + # Detect parser for this attribute + parser = cls.__dataclass_fields__[attr].metadata.get('parser') + if parser: + value_fmt = parser(value) + + return value_fmt + @classmethod def get_base_type(cls, attr: str): return cls._get_metadata(attr)['base_type'] @@ -406,12 +420,12 @@ class FglProperties(Properties): 'precision': 0.1, 'read_only': False }) - display_temperature: int = field(default=25, - metadata={ + display_temperature: float = field(default=25, + metadata={ 'base_type': 'integer', - 'precision': 0.1, - 'read_only': True - }) + 'read_only': True, + 'parser': lambda x: round((x-5000)/50)/2, + }) af_vertical_direction: int = field(default=3, metadata={ 'base_type': 'integer', @@ -478,12 +492,12 @@ class FglBProperties(Properties): 'precision': 0.1, 'read_only': False }) - display_temperature: int = field(default=25, - metadata={ - 'base_type': 'integer', - 'precision': 0.1, - 'read_only': True - }) + display_temperature: float = field(default=25, + metadata={ + 'base_type': 'float', + 'read_only': True, + 'parser': lambda x: round((x-5000)/50)/2, + }) af_vertical_move_step1: int = field(default=3, metadata={ 'base_type': 'integer', diff --git a/aircon/query_handlers.py b/aircon/query_handlers.py index da4cbed..34d08ad 100644 --- a/aircon/query_handlers.py +++ b/aircon/query_handlers.py @@ -85,9 +85,10 @@ async def property_update_handler(self, request: web.Request) -> web.Response: # Fix A/C typos. if name == 'f_votage': name = 'f_voltage' - data_type = device.get_property_type(name) - value = data_type(update['data']['value']) + value = device.parse_property(name, update['data']['value']) + logging.debug(f"Updating {device}.{name} to {value} ({update['data']['value']})") device.update_property(name, value) + logging.debug(f"Updated{device}: {device.get_all_properties()})") except Exception as ex: logging.error('Failed to handle {}. Exception = {}'.format(update, ex)) #TODO: Should return internal error?