X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=smart_home%2Flights.py;h=908fe8d3ed3550c809af1678fa905b1311336728;hb=322c0be2cc5266fadd5bac90cf1082ba66611a92;hp=64f2105ffe8a4de0864e95a14d2b05703010a2bb;hpb=07a9659282a6b7c7776eec85f0708de9f8815374;p=python_utils.git diff --git a/smart_home/lights.py b/smart_home/lights.py index 64f2105..908fe8d 100644 --- a/smart_home/lights.py +++ b/smart_home/lights.py @@ -2,7 +2,6 @@ """Utilities for dealing with the smart lights.""" -from abc import abstractmethod import datetime import json import logging @@ -10,10 +9,11 @@ import os import re import subprocess import sys +from abc import abstractmethod from typing import Any, Dict, List, Optional, Tuple -from overrides import overrides import tinytuya as tt +from overrides import overrides import ansi import argparse_utils @@ -21,8 +21,8 @@ import arper import config import logging_utils import smart_home.device as dev -from google_assistant import ask_google, GoogleResponse -from decorator_utils import timeout, memoized +from decorator_utils import memoized, timeout +from google_assistant import GoogleResponse, ask_google logger = logging.getLogger(__name__) @@ -39,9 +39,7 @@ args.add_argument( ) -@timeout( - 5.0, use_signals=False, error_message="Timed out waiting for tplink.py" -) +@timeout(5.0, use_signals=False, error_message="Timed out waiting for tplink.py") def tplink_light_command(command: str) -> bool: result = os.system(command) signal = result & 0xFF @@ -57,11 +55,13 @@ def tplink_light_command(command: str) -> bool: logger.warning(msg) logging_utils.hlog(msg) return False - logger.debug(f'{command} succeeded.') + logger.debug('%s succeeded.', command) return True class BaseLight(dev.Device): + """A base class representing a smart light.""" + def __init__(self, name: str, mac: str, keywords: str = "") -> None: super().__init__(name.strip(), mac.strip(), keywords) @@ -69,9 +69,9 @@ class BaseLight(dev.Device): def parse_color_string(color: str) -> Optional[Tuple[int, int, int]]: m = re.match( 'r#?([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])', - color + color, ) - if m is not None and len(m.group) == 3: + if m is not None and len(m.groups()) == 3: red = int(m.group(0), 16) green = int(m.group(1), 16) blue = int(m.group(2), 16) @@ -113,8 +113,7 @@ class BaseLight(dev.Device): class GoogleLight(BaseLight): - def __init__(self, name: str, mac: str, keywords: str = "") -> None: - super().__init__(name, mac, keywords) + """A smart light controlled by talking to Google.""" def goog_name(self) -> str: name = self.get_name() @@ -126,15 +125,11 @@ class GoogleLight(BaseLight): @overrides def turn_on(self) -> bool: - return GoogleLight.parse_google_response( - ask_google(f"turn {self.goog_name()} on") - ) + return GoogleLight.parse_google_response(ask_google(f"turn {self.goog_name()} on")) @overrides def turn_off(self) -> bool: - return GoogleLight.parse_google_response( - ask_google(f"turn {self.goog_name()} off") - ) + return GoogleLight.parse_google_response(ask_google(f"turn {self.goog_name()} off")) @overrides def status(self) -> str: @@ -147,7 +142,9 @@ class GoogleLight(BaseLight): r = ask_google(f"is {self.goog_name()} on?") if not r.success: return False - return 'is on' in r.audio_transcription + if r.audio_transcription is not None: + return 'is on' in r.audio_transcription + raise Exception("Can't reach Google?!") @overrides def is_off(self) -> bool: @@ -163,11 +160,12 @@ class GoogleLight(BaseLight): # the bookcase one is set to 40% bright txt = r.audio_transcription - m = re.search(r"(\d+)% bright", txt) - if m is not None: - return int(m.group(1)) - if "is off" in txt: - return 0 + if txt is not None: + m = re.search(r"(\d+)% bright", txt) + if m is not None: + return int(m.group(1)) + if "is off" in txt: + return 0 return None @overrides @@ -186,12 +184,12 @@ class GoogleLight(BaseLight): @overrides def make_color(self, color: str) -> bool: - return GoogleLight.parse_google_response( - ask_google(f"make {self.goog_name()} {color}") - ) + return GoogleLight.parse_google_response(ask_google(f"make {self.goog_name()} {color}")) class TuyaLight(BaseLight): + """A Tuya smart light.""" + ids_by_mac = { '68:C6:3A:DE:1A:94': '8844664268c63ade1a94', '68:C6:3A:DE:27:1A': '8844664268c63ade271a', @@ -256,14 +254,14 @@ class TuyaLight(BaseLight): @overrides def set_dimmer_level(self, level: int) -> bool: - logger.debug(f'Setting brightness to {level}') + logger.debug('Setting brightness to %d', level) self.bulb.set_brightness(level) return True @overrides def make_color(self, color: str) -> bool: rgb = BaseLight.parse_color_string(color) - logger.debug(f'Light color: {color} -> {rgb}') + logger.debug('Light color: %s -> %s', color, rgb) if rgb is not None: self.bulb.set_colour(rgb[0], rgb[1], rgb[2]) return True @@ -271,16 +269,19 @@ class TuyaLight(BaseLight): class TPLinkLight(BaseLight): + """A TPLink smart light.""" + def __init__(self, name: str, mac: str, keywords: str = "") -> None: super().__init__(name, mac, keywords) self.children: List[str] = [] self.info: Optional[Dict] = None self.info_ts: Optional[datetime.datetime] = None - if "children" in self.keywords: - self.info = self.get_info() - if self.info is not None: - for child in self.info["children"]: - self.children.append(child["id"]) + if self.keywords is not None: + if "children" in self.keywords: + self.info = self.get_info() + if self.info is not None: + for child in self.info["children"]: + self.children.append(child["id"]) @memoized def get_tplink_name(self) -> Optional[str]: @@ -301,13 +302,11 @@ class TPLinkLight(BaseLight): def get_children(self) -> List[str]: return self.children - def command( - self, cmd: str, child: str = None, extra_args: str = None - ) -> bool: + def command(self, cmd: str, child: str = None, extra_args: str = None) -> bool: cmd = self.get_cmdline(child) + f"-c {cmd}" if extra_args is not None: cmd += f" {extra_args}" - logger.debug(f'About to execute {cmd}') + logger.debug('About to execute: %s', cmd) return tplink_light_command(cmd) @overrides @@ -333,18 +332,17 @@ class TPLinkLight(BaseLight): def make_color(self, color: str) -> bool: raise NotImplementedError - @timeout( - 10.0, use_signals=False, error_message="Timed out waiting for tplink.py" - ) + @timeout(10.0, use_signals=False, error_message="Timed out waiting for tplink.py") def get_info(self) -> Optional[Dict]: cmd = self.get_cmdline() + "-c info" + logger.debug('Getting status of %s via "%s"...', self.mac, cmd) out = subprocess.getoutput(cmd) - logger.debug(f'RAW OUT> {out}') + logger.debug('RAW OUT> %s', out) out = re.sub("Sent:.*\n", "", out) out = re.sub("Received: *", "", out) try: self.info = json.loads(out)["system"]["get_sysinfo"] - logger.debug(json.dumps(self.info, indent=4, sort_keys=True)) + logger.debug("%s", json.dumps(self.info, indent=4, sort_keys=True)) self.info_ts = datetime.datetime.now() return self.info except Exception as e: