more rich work
This commit is contained in:
60
console.py
60
console.py
@@ -4,26 +4,42 @@ import re
|
|||||||
from appdaemon.adapi import ADAPI
|
from appdaemon.adapi import ADAPI
|
||||||
from appdaemon.logging import AppNameFormatter
|
from appdaemon.logging import AppNameFormatter
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.highlighter import NullHighlighter, RegexHighlighter
|
from rich.highlighter import RegexHighlighter
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
from rich.theme import Theme
|
from rich.theme import Theme
|
||||||
|
|
||||||
|
|
||||||
|
class RCHighlighter(RegexHighlighter):
|
||||||
|
highlights = [
|
||||||
|
r'(?P<light>(light|switch)\.\w+)',
|
||||||
|
r'(?P<time>\d+:\d+:\d+)',
|
||||||
|
r'(?P<z2m>zigbee2mqtt/)',
|
||||||
|
r'(?P<sensor>binary_sensor\.\w+)',
|
||||||
|
# r"'state': '(?P<on>on)|(?P<off>off)'"
|
||||||
|
r'(?P<true>True)|(?P<false>False)',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
console = Console(
|
console = Console(
|
||||||
width=150,
|
width=150,
|
||||||
theme=Theme({
|
theme=Theme(
|
||||||
'log.time': 'none',
|
{
|
||||||
'logging.level.info': 'none',
|
'log.time': 'none',
|
||||||
|
'logging.level.info': 'none',
|
||||||
'room': 'italic bright_cyan',
|
'room': 'italic bright_cyan',
|
||||||
'component': 'dark_violet',
|
'component': 'dark_violet',
|
||||||
|
'friendly_name': 'yellow',
|
||||||
'entity_id': 'light_slate_blue',
|
'light': 'light_slate_blue',
|
||||||
'time': 'yellow',
|
'sensor': 'green',
|
||||||
|
'time': 'yellow',
|
||||||
'z2m': 'bright_black',
|
'z2m': 'bright_black',
|
||||||
'topic': 'chartreuse2',
|
'topic': 'chartreuse2',
|
||||||
}),
|
'true': 'green',
|
||||||
|
'false': 'red',
|
||||||
|
}
|
||||||
|
),
|
||||||
log_time_format='%Y-%m-%d %I:%M:%S %p',
|
log_time_format='%Y-%m-%d %I:%M:%S %p',
|
||||||
|
highlighter=RCHighlighter(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +63,7 @@ class RoomControllerFormatter(logging.Formatter):
|
|||||||
|
|
||||||
datefmt = '%Y-%m-%d %I:%M:%S %p'
|
datefmt = '%Y-%m-%d %I:%M:%S %p'
|
||||||
style = '{'
|
style = '{'
|
||||||
validate=True
|
validate = True
|
||||||
|
|
||||||
super().__init__(fmt, datefmt, style, validate)
|
super().__init__(fmt, datefmt, style, validate)
|
||||||
# console.print(f'Format: [bold yellow]{fmt}[/]')
|
# console.print(f'Format: [bold yellow]{fmt}[/]')
|
||||||
@@ -61,14 +77,6 @@ class RoomControllerFormatter(logging.Formatter):
|
|||||||
return super().format(record)
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
class RCHighlighter(RegexHighlighter):
|
|
||||||
highlights = [
|
|
||||||
r"(?P<entity_id>(light|switch)\.\w+)",
|
|
||||||
r'(?P<time>\d+:\d+:\d+)',
|
|
||||||
r'(?P<z2m>zigbee2mqtt/)'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def new_handler() -> RichHandler:
|
def new_handler() -> RichHandler:
|
||||||
return RichHandler(
|
return RichHandler(
|
||||||
console=console,
|
console=console,
|
||||||
@@ -93,16 +101,14 @@ def setup_component_logging(self):
|
|||||||
self.logger = logger.getChild(typ)
|
self.logger = logger.getChild(typ)
|
||||||
if len(self.logger.handlers) == 0:
|
if len(self.logger.handlers) == 0:
|
||||||
self.logger.setLevel(logging.INFO)
|
self.logger.setLevel(logging.INFO)
|
||||||
self.logger.addHandler(setup_handler(room=self.args["app"], component=typ))
|
self.logger.addHandler(setup_handler(room=self.args['app'], component=typ))
|
||||||
self.logger.propagate = False
|
self.logger.propagate = False
|
||||||
|
|
||||||
|
|
||||||
def init_logging(self: ADAPI, level):
|
def init_logging(self: ADAPI, level):
|
||||||
for h in logging.getLogger('AppDaemon').handlers:
|
for h in logging.getLogger('AppDaemon').handlers:
|
||||||
og_formatter = h.formatter
|
og_formatter = h.formatter
|
||||||
h.setFormatter(
|
h.setFormatter(UnMarkupFormatter(fmt=og_formatter._fmt, datefmt=og_formatter.datefmt, style='{'))
|
||||||
UnMarkupFormatter(fmt=og_formatter._fmt, datefmt=og_formatter.datefmt, style='{')
|
|
||||||
)
|
|
||||||
|
|
||||||
if not any(isinstance(h, RichHandler) for h in self.logger.handlers):
|
if not any(isinstance(h, RichHandler) for h in self.logger.handlers):
|
||||||
self.logger.propagate = False
|
self.logger.propagate = False
|
||||||
|
|||||||
18
motion.py
18
motion.py
@@ -30,6 +30,9 @@ class Motion(Hass):
|
|||||||
self.app: RoomController = self.get_app(self.args['app'])
|
self.app: RoomController = self.get_app(self.args['app'])
|
||||||
self.log(f'Connected to AD app [room]{self.app.name}[/]')
|
self.log(f'Connected to AD app [room]{self.app.name}[/]')
|
||||||
|
|
||||||
|
assert self.entity_exists(self.args['sensor'])
|
||||||
|
assert self.entity_exists(self.args['ref_entity'])
|
||||||
|
|
||||||
base_kwargs = dict(
|
base_kwargs = dict(
|
||||||
entity_id=self.ref_entity.entity_id,
|
entity_id=self.ref_entity.entity_id,
|
||||||
immediate=True, # avoids needing to sync the state
|
immediate=True, # avoids needing to sync the state
|
||||||
@@ -48,7 +51,9 @@ class Motion(Hass):
|
|||||||
oneshot=True,
|
oneshot=True,
|
||||||
cause='motion on',
|
cause='motion on',
|
||||||
)
|
)
|
||||||
self.log(f'Waiting for motion on {self.sensor.friendly_name}')
|
self.log(f'Waiting for motion on [friendly_name]{self.sensor.friendly_name}[/]')
|
||||||
|
if self.sensor_state:
|
||||||
|
self.log(f'{self.sensor.friendly_name} is already on', level='WARNING')
|
||||||
|
|
||||||
def listen_motion_off(self, duration: timedelta):
|
def listen_motion_off(self, duration: timedelta):
|
||||||
"""Sets up the motion off callback to deactivate the room"""
|
"""Sets up the motion off callback to deactivate the room"""
|
||||||
@@ -61,18 +66,21 @@ class Motion(Hass):
|
|||||||
oneshot=True,
|
oneshot=True,
|
||||||
cause='motion off',
|
cause='motion off',
|
||||||
)
|
)
|
||||||
self.log(f'Waiting for motion to stop on {self.sensor.friendly_name} for {duration}')
|
self.log(f'Waiting for motion to stop on [friendly_name]{self.sensor.friendly_name}[/] for {duration}')
|
||||||
|
|
||||||
|
if not self.sensor_state:
|
||||||
|
self.log(f'{self.sensor.friendly_name} is currently off', level='WARNING')
|
||||||
|
|
||||||
def callback_light_on(self, entity=None, attribute=None, old=None, new=None, kwargs=None):
|
def callback_light_on(self, entity=None, attribute=None, old=None, new=None, kwargs=None):
|
||||||
"""Called when the light turns on"""
|
"""Called when the light turns on"""
|
||||||
if new is not None:
|
if new is not None:
|
||||||
self.log(f'{entity} turned on')
|
self.log(f'Detected {entity} turning on', level='DEBUG')
|
||||||
duration = self.app.off_duration()
|
duration = self.app.off_duration()
|
||||||
self.listen_motion_off(duration)
|
self.listen_motion_off(duration)
|
||||||
|
|
||||||
def callback_light_off(self, entity=None, attribute=None, old=None, new=None, kwargs=None):
|
def callback_light_off(self, entity=None, attribute=None, old=None, new=None, kwargs=None):
|
||||||
"""Called when the light turns off"""
|
"""Called when the light turns off"""
|
||||||
self.log(f'{entity} turned off')
|
self.log(f'Detected {entity} turning off', level='DEBUG')
|
||||||
self.listen_motion_on()
|
self.listen_motion_on()
|
||||||
|
|
||||||
def get_app_callbacks(self, name: str = None):
|
def get_app_callbacks(self, name: str = None):
|
||||||
@@ -100,4 +108,4 @@ class Motion(Hass):
|
|||||||
if (m := re.match('new=(?P<new>.*?)\s', kwargs)) is not None:
|
if (m := re.match('new=(?P<new>.*?)\s', kwargs)) is not None:
|
||||||
new = m.group('new')
|
new = m.group('new')
|
||||||
self.cancel_listen_state(handle)
|
self.cancel_listen_state(handle)
|
||||||
self.log(f'cancelled callback for sensor {entity} turning {new}')
|
self.log(f'cancelled callback for sensor {entity} turning {new}', level='DEBUG')
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ from appdaemon.entity import Entity
|
|||||||
from appdaemon.plugins.hass.hassapi import Hass
|
from appdaemon.plugins.hass.hassapi import Hass
|
||||||
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
||||||
from astral import SunDirection
|
from astral import SunDirection
|
||||||
from console import setup_handler
|
from console import console, setup_handler
|
||||||
from rich.table import Table
|
from rich.console import Console, ConsoleOptions, RenderResult, Group
|
||||||
|
from rich.table import Table, Column
|
||||||
|
|
||||||
|
|
||||||
def str_to_timedelta(input_str: str) -> datetime.timedelta:
|
def str_to_timedelta(input_str: str) -> datetime.timedelta:
|
||||||
@@ -63,6 +64,12 @@ class RoomState:
|
|||||||
def from_json(cls, json_input):
|
def from_json(cls, json_input):
|
||||||
return cls(**json_input)
|
return cls(**json_input)
|
||||||
|
|
||||||
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
||||||
|
table = Table('Entity ID', 'State')
|
||||||
|
for name, state in self.scene.items():
|
||||||
|
table.add_row(name, str(state))
|
||||||
|
yield table
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RoomConfig:
|
class RoomConfig:
|
||||||
@@ -90,6 +97,17 @@ class RoomConfig:
|
|||||||
cfg: Dict = yaml.load(f, Loader=yaml.SafeLoader)[app_name]
|
cfg: Dict = yaml.load(f, Loader=yaml.SafeLoader)[app_name]
|
||||||
return cls.from_app_config(cfg)
|
return cls.from_app_config(cfg)
|
||||||
|
|
||||||
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
||||||
|
table = Table(
|
||||||
|
Column('Time', width=15),
|
||||||
|
Column('Scene'),
|
||||||
|
highlight=True, padding=1, collapse_padding=True,
|
||||||
|
)
|
||||||
|
for state in self.states:
|
||||||
|
lines = [f'{name:20}{state["state"]} Brightness: {state["brightness"]:<4} Temp: {state["color_temp"]}' for name, state in state.scene.items()]
|
||||||
|
table.add_row(state.time.strftime('%I:%M:%S %p'), '\n'.join(lines))
|
||||||
|
yield table
|
||||||
|
|
||||||
def rich_table(self, app_name: str) -> Table:
|
def rich_table(self, app_name: str) -> Table:
|
||||||
table = Table(title=app_name, expand=True, highlight=True, padding=1, collapse_padding=True)
|
table = Table(title=app_name, expand=True, highlight=True, padding=1, collapse_padding=True)
|
||||||
table.add_column('Time')
|
table.add_column('Time')
|
||||||
@@ -154,7 +172,7 @@ class RoomController(Hass, Mqtt):
|
|||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.logger = logger.getChild(self.name)
|
self.logger = logger.getChild(self.name)
|
||||||
if not self.logger.hasHandlers():
|
if not self.logger.hasHandlers():
|
||||||
self.logger.setLevel(logging.INFO)
|
self.logger.setLevel(self.args.get('rich', logging.INFO))
|
||||||
self.logger.addHandler(setup_handler(room=self.name))
|
self.logger.addHandler(setup_handler(room=self.name))
|
||||||
# console.log(f'[yellow]Added RichHandler to {self.logger.name}[/]')
|
# console.log(f'[yellow]Added RichHandler to {self.logger.name}[/]')
|
||||||
|
|
||||||
@@ -209,9 +227,9 @@ class RoomController(Hass, Mqtt):
|
|||||||
|
|
||||||
assert isinstance(state.time, datetime.time), f'Invalid time: {state.time}'
|
assert isinstance(state.time, datetime.time), f'Invalid time: {state.time}'
|
||||||
|
|
||||||
# if self.rich_logging:
|
if self.logger.isEnabledFor(logging.DEBUG):
|
||||||
# table = self._room_config.rich_table(self.name)
|
# table = self._room_config.rich_table(self.name)
|
||||||
# console.log(table, highlight=False)
|
console.print(self._room_config)
|
||||||
|
|
||||||
self.states = sorted(self.states, key=lambda s: s.time, reverse=True)
|
self.states = sorted(self.states, key=lambda s: s.time, reverse=True)
|
||||||
|
|
||||||
@@ -245,12 +263,10 @@ class RoomController(Hass, Mqtt):
|
|||||||
|
|
||||||
def current_scene(self, now: datetime.time = None) -> Dict[str, Dict[str, str | int]]:
|
def current_scene(self, now: datetime.time = None) -> Dict[str, Dict[str, str | int]]:
|
||||||
state = self.current_state(now)
|
state = self.current_state(now)
|
||||||
# print(f'{type(state).__name__}')
|
assert type(state).__name__ == 'RoomState' # needed this way instead of isinstance(...) for the reloading to work
|
||||||
# assert isinstance(state, RoomState), f'Invalid state: {type(state).__name__}'
|
if self.logger.isEnabledFor(logging.DEBUG):
|
||||||
assert type(state).__name__ == 'RoomState' # needed for the reloading to work
|
self.log('Current scene:')
|
||||||
# self.log(f'Current scene: {state}')
|
console.print(state)
|
||||||
self.log('Current scene:')
|
|
||||||
self.log(state)
|
|
||||||
return state.scene
|
return state.scene
|
||||||
|
|
||||||
def app_entity_states(self) -> Dict[str, str]:
|
def app_entity_states(self) -> Dict[str, str]:
|
||||||
@@ -329,7 +345,9 @@ class RoomController(Hass, Mqtt):
|
|||||||
scene[entity]['state'] = 'on'
|
scene[entity]['state'] = 'on'
|
||||||
|
|
||||||
self.call_service('scene/apply', entities=scene, transition=0)
|
self.call_service('scene/apply', entities=scene, transition=0)
|
||||||
self.log(f'Applied scene: {scene}')
|
if self.logger.isEnabledFor(logging.INFO):
|
||||||
|
self.log('Applied scene:')
|
||||||
|
console.print(scene)
|
||||||
|
|
||||||
elif scene is None:
|
elif scene is None:
|
||||||
self.log('No scene, ignoring...')
|
self.log('No scene, ignoring...')
|
||||||
|
|||||||
Reference in New Issue
Block a user