Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ ENV/

# mypy
.mypy_cache/

config_*.json
3 changes: 3 additions & 0 deletions aircon/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions aircon/aircon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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']
Expand All @@ -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']
Expand Down
6 changes: 4 additions & 2 deletions aircon/mqtt_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/#')

Expand Down
36 changes: 25 additions & 11 deletions aircon/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
5 changes: 3 additions & 2 deletions aircon/query_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down