Skip to content

Commit

Permalink
Remove Data class
Browse files Browse the repository at this point in the history
  • Loading branch information
karlkar authored and KarolKsionek committed Sep 28, 2020
1 parent 6f44632 commit 9a151e8
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 194 deletions.
17 changes: 8 additions & 9 deletions aircon/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
from .app_mappings import SECRET_MAP
from .config import Config
from .error import Error
from .aircon import BaseDevice, AcDevice, FglDevice, FglBProperties, HumidifierDevice
from .aircon import BaseDevice, AcDevice, FglDevice, FglBDevice, HumidifierDevice
from .discovery import perform_discovery
from .store import Data
from .mqtt_client import MqttClient
from .query_handlers import QueryHandlers

Expand All @@ -31,12 +30,12 @@ class KeepAliveThread(threading.Thread):

_KEEP_ALIVE_INTERVAL = 10.0

def __init__(self, host: str, port: int, data: Data):
def __init__(self, host: str, port: int, device: BaseDevice):
self.run_lock = threading.Condition()
self._alive = False
self._host = host
self._port = port
self._data = data
self._device = device
local_ip = self._get_local_ip()
self._headers = {
'Accept': 'application/json',
Expand Down Expand Up @@ -97,9 +96,9 @@ def run(self) -> None:
self._establish_connection(conn)
except:
logging.exception('Failed to send local_reg keep alive to the AC.')
logging.debug('[KeepAlive] Waiting for notification or timeout %d', self._data.commands_queue.qsize())
logging.debug('[KeepAlive] Waiting for notification or timeout %d', self._device.commands_queue.qsize())
self._json['local_reg']['notify'] = int(
self._data.commands_queue.qsize() > 0 or self.run_lock.wait(self._KEEP_ALIVE_INTERVAL))
self._device.commands_queue.qsize() > 0 or self.run_lock.wait(self._KEEP_ALIVE_INTERVAL))

class QueryStatusThread(threading.Thread):
"""Thread to preiodically query the status of all properties.
Expand All @@ -119,7 +118,7 @@ def run(self) -> None:
while True:
# In case the AC is stuck, and not fetching commands, avoid flooding
# the queue with status updates.
while self._device.data.commands_queue.qsize() > 10:
while self._device.commands_queue.qsize() > 10:
time.sleep(self._WAIT_FOR_EMPTY_QUEUE)
self._device.queue_status()
if _keep_alive:
Expand Down Expand Up @@ -290,15 +289,15 @@ def run(parsed_args):
mqtt_client.username_pw_set(*parsed_args.mqtt_user.split(':',1))
mqtt_client.connect(parsed_args.mqtt_host, parsed_args.mqtt_port)
mqtt_client.loop_start()
data.change_listener = mqtt_client.mqtt_publish_update
device.change_listener = mqtt_client.mqtt_publish_update

global _keep_alive
_keep_alive = None # type: typing.Optional[KeepAliveThread]

query_status = QueryStatusThread(device)
query_status.start()

_keep_alive = KeepAliveThread(parsed_args.ip, parsed_args.port, device.data)
_keep_alive = KeepAliveThread(parsed_args.ip, parsed_args.port, device)
_keep_alive.start()

httpd = HTTPServer(('', parsed_args.port), MakeHttpRequestHandlerClass(config, device))
Expand Down
89 changes: 69 additions & 20 deletions aircon/aircon.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,79 @@
The code here relies on Python 3.7
"""
from copy import deepcopy
from dataclasses import fields
import enum
import logging
import random
import string
import threading
from typing import Callable
import queue
from Crypto.Cipher import AES

from .error import Error
from .properties import AcProperties, FastColdHeat, FglProperties, FglBProperties, HumidifierProperties
from .store import Data
from .properties import AcProperties, FastColdHeat, FglProperties, FglBProperties, HumidifierProperties, Properties

class BaseDevice:
def __init__(self, data: Data):
self.data = data
def __init__(self, properties: Properties):
self._properties = properties
self._properties_lock = threading.Lock()

self._next_command_id = 0

self.commands_queue = queue.Queue()
self._commands_seq_no = 0
self._commands_seq_no_lock = threading.Lock()

self._updates_seq_no = 0
self._updates_seq_no_lock = threading.Lock()

self.change_listener: Callable[[str, str], None] = None

def get_all_properties(self) -> Properties:
with self._properties_lock:
return deepcopy(self._properties)

def get_property(self, name: str):
"""Get a stored property."""
with self._properties_lock:
return getattr(self._properties, name)

def get_property_type(self, name: str):
return self._properties.get_type(name)

def update_property(self, name: str, value) -> None:
"""Update the stored properties, if changed."""
with self._properties_lock:
old_value = getattr(self._properties, name)
if value != old_value:
setattr(self._properties, name, value)
logging.debug('Updated properties: %s' % self._properties)
if self.change_listener:
self.change_listener(name, value)

def get_command_seq_no(self) -> int:
with self._commands_seq_no_lock:
seq_no = self._commands_seq_no
self._commands_seq_no += 1
return seq_no

def is_update_valid(self, cur_update_no: int) -> bool:
with self._updates_seq_no_lock:
# Every once in a while the sequence number is zeroed out, so accept it.
if self._updates_seq_no > cur_update_no and cur_update_no > 0:
logging.error('Stale update found %d. Last update used is %d.',
cur_update_no, self._updates_seq_no)
return False # Old update
self._updates_seq_no = cur_update_no
return True

def queue_command(self, name: str, value) -> None:
if self.data.properties.get_read_only(name):
if self._properties.get_read_only(name):
raise Error('Cannot update read-only property "{}".'.format(name))
data_type = self.data.properties.get_type(name)
base_type = self.data.properties.get_base_type(name)
data_type = self._properties.get_type(name)
base_type = self._properties.get_base_type(name)
if issubclass(data_type, enum.Enum):
data_value = data_type[value].value
elif data_type is int and type(value) is str and '.' in value:
Expand All @@ -62,8 +115,8 @@ def queue_command(self, name: str, value) -> None:
# There are (usually) no acks on commands, so also queue an update to the
# property, to be run once the command is sent.
typed_value = data_type[value] if issubclass(data_type, enum.Enum) else data_value
property_updater = lambda: self.data.update_property(name, typed_value)
self.data.commands_queue.put_nowait((command, property_updater))
property_updater = lambda: self.update_property(name, typed_value)
self.commands_queue.put_nowait((command, property_updater))

# Handle turning on FastColdHeat
if name == 't_temp_heatcold' and typed_value is FastColdHeat.ON:
Expand All @@ -73,7 +126,7 @@ def queue_command(self, name: str, value) -> None:
self.queue_command('t_temp_eight', 'OFF')

def queue_status(self) -> None:
for data_field in fields(self.data.properties):
for data_field in fields(self._properties):
command = {
'cmds': [{
'cmd': {
Expand All @@ -86,24 +139,20 @@ def queue_status(self) -> None:
}]
}
self._next_command_id += 1
self.data.commands_queue.put_nowait((command, None))
self.commands_queue.put_nowait((command, None))

class AcDevice(BaseDevice):
def __init__(self):
data = Data(properties=AcProperties())
super().__init__(data)
super().__init__(properties=AcProperties())

class FglDevice(BaseDevice):
def __init__(self):
data = Data(properties=FglProperties())
super().__init__(data)
super().__init__(properties=FglProperties())

class FglBProperties(BaseDevice):
class FglBDevice(BaseDevice):
def __init__(self):
data = Data(properties=FglBProperties())
super().__init__(data)
super().__init__(properties=FglBProperties())

class HumidifierDevice(BaseDevice):
def __init__(self):
data = Data(properties=HumidifierDevice())
super().__init__(data)
super().__init__(properties=HumidifierDevice())
5 changes: 2 additions & 3 deletions aircon/mqtt_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from . import aircon
from .aircon import BaseDevice
from .store import Data
from .properties import AcWorkMode

class MqttClient(mqtt.Client):
Expand All @@ -19,7 +18,7 @@ def __init__(self, client_id: str, mqtt_topics: dict, device: BaseDevice):

def mqtt_on_connect(self, client: mqtt.Client, userdata, flags, rc):
client.subscribe([(self._mqtt_topics['sub'].format(data_field.name), 0)
for data_field in fields(self._device.data.properties)])
for data_field in fields(self._device.get_all_properties())])
# Subscribe to subscription updates.
client.subscribe('$SYS/broker/log/M/subscribe/#')

Expand All @@ -44,7 +43,7 @@ def mqtt_on_subscribe(self, payload: bytes):
if topic not in self._mqtt_topics['pub']:
return
name = topic.rsplit('/', 2)[1]
self.mqtt_publish_update(name, self._device.data.get_property(name))
self.mqtt_publish_update(name, self._device.get_property(name))

def mqtt_publish_update(self, name: str, value) -> None:
if isinstance(value, enum.Enum):
Expand Down
111 changes: 1 addition & 110 deletions aircon/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,113 +2,7 @@
from dataclasses_json import dataclass_json
import enum

class AirFlow(enum.IntEnum):
OFF = 0
VERTICAL_ONLY = 1
HORIZONTAL_ONLY = 2
VERTICAL_AND_HORIZONTAL = 3

class FanSpeed(enum.IntEnum):
AUTO = 0
LOWER = 5
LOW = 6
MEDIUM = 7
HIGH = 8
HIGHER = 9

class SleepMode(enum.IntEnum):
STOP = 0
ONE = 1
TWO = 2
THREE = 3
FOUR = 4

class StateMachine(enum.IntEnum):
FANONLY = 0
HEAT = 1
COOL = 2
DRY = 3
AUTO = 4
FAULTSHIELD = 5
POWEROFF = 6
OFFLINE = 7
READONLYSHARED = 8

class AcWorkMode(enum.IntEnum):
FAN = 0
HEAT = 1
COOL = 2
DRY = 3
AUTO = 4

class AirFlow(enum.Enum):
OFF = 0
ON = 1

class DeviceErrorStatus(enum.Enum):
NORMALSTATE = 0
FAULTSTATE = 1

class Dimmer(enum.Enum):
ON = 0
OFF = 1

class DoubleFrequency(enum.Enum):
OFF = 0
class AirFlow(enum.IntEnum):
OFF = 0
VERTICAL_ONLY = 1
HORIZONTAL_ONLY = 2
VERTICAL_AND_HORIZONTAL = 3

class FanSpeed(enum.IntEnum):
AUTO = 0
LOWER = 5
LOW = 6
MEDIUM = 7
HIGH = 8
HIGHER = 9

class SleepMode(enum.IntEnum):
STOP = 0
ONE = 1
TWO = 2
THREE = 3
FOUR = 4

class StateMachine(enum.IntEnum):
FANONLY = 0
HEAT = 1
COOL = 2
DRY = 3
AUTO = 4
FAULTSHIELD = 5
POWEROFF = 6
OFFLINE = 7
READONLYSHARED = 8

class AcWorkMode(enum.IntEnum):
FAN = 0
HEAT = 1
COOL = 2
DRY = 3
AUTO = 4

class AirFlow(enum.Enum):
OFF = 0
ON = 1

class DeviceErrorStatus(enum.Enum):
NORMALSTATE = 0
FAULTSTATE = 1

class Dimmer(enum.Enum):
ON = 0
OFF = 1

class DoubleFrequency(enum.Enum):
OFF = 0
class AirFlow(enum.IntEnum):
class AirFlowState(enum.IntEnum):
OFF = 0
VERTICAL_ONLY = 1
HORIZONTAL_ONLY = 2
Expand Down Expand Up @@ -221,8 +115,6 @@ class FglFanSpeed(enum.IntEnum):
HIGH = 3
AUTO = 4



class Properties(object):
@classmethod
def _get_metadata(cls, attr: str):
Expand All @@ -240,7 +132,6 @@ def get_base_type(cls, attr: str):
def get_read_only(cls, attr: str):
return cls._get_metadata(attr)['read_only']


@dataclass_json
@dataclass
class AcProperties(Properties):
Expand Down
Loading

0 comments on commit 9a151e8

Please sign in to comment.