seperate rich logging system
This commit is contained in:
@@ -2,7 +2,7 @@ import json
|
||||
from dataclasses import dataclass
|
||||
|
||||
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
||||
from console import init_logging
|
||||
from console import setup_component_logging
|
||||
|
||||
from room_control import RoomController
|
||||
|
||||
@@ -13,11 +13,9 @@ class Button(Mqtt):
|
||||
rich: bool = False
|
||||
|
||||
async def initialize(self):
|
||||
if level := self.args.get('rich', False):
|
||||
self.rich = True
|
||||
init_logging(self, level)
|
||||
|
||||
setup_component_logging(self)
|
||||
self.app: RoomController = await self.get_app(self.args['app'])
|
||||
self.log(f'Connected to AD app [room]{self.app.name}[/]')
|
||||
|
||||
self.button = self.args['button']
|
||||
self.setup_buttons(self.button)
|
||||
|
||||
56
console.py
56
console.py
@@ -8,9 +8,15 @@ from rich.highlighter import NullHighlighter
|
||||
from rich.logging import RichHandler
|
||||
from rich.theme import Theme
|
||||
|
||||
|
||||
console = Console(
|
||||
width=150,
|
||||
theme=Theme({'appname': 'italic bright_cyan'}),
|
||||
theme=Theme({
|
||||
# 'appname': 'italic bright_cyan',
|
||||
'room': 'italic bright_cyan',
|
||||
'component': 'violet'
|
||||
}),
|
||||
log_time_format='%Y-%m-%d %I:%M:%S %p',
|
||||
)
|
||||
|
||||
|
||||
@@ -22,8 +28,34 @@ class UnMarkupFormatter(AppNameFormatter):
|
||||
return self.md_regex.sub(r'\g<text>', result)
|
||||
|
||||
|
||||
def create_handler() -> RichHandler:
|
||||
handler = RichHandler(
|
||||
class RoomControllerFormatter(logging.Formatter):
|
||||
def __init__(self, room: str, component: str = None):
|
||||
self.log_fields = {'room': room}
|
||||
|
||||
fmt = '[room]{room:>12}[/]'
|
||||
if component is not None:
|
||||
fmt += ' [component]{component:<9}[/]'
|
||||
self.log_fields['component'] = component
|
||||
fmt += ' {message}'
|
||||
|
||||
datefmt = '%Y-%m-%d %I:%M:%S %p'
|
||||
style = '{'
|
||||
validate=True
|
||||
|
||||
super().__init__(fmt, datefmt, style, validate)
|
||||
# console.print(f'Format: [bold yellow]{fmt}[/]')
|
||||
|
||||
def format(self, record: logging.LogRecord):
|
||||
parts = record.name.split('.')
|
||||
record.room = parts[1]
|
||||
if len(parts) == 3:
|
||||
record.component = parts[2]
|
||||
|
||||
return super().format(record)
|
||||
|
||||
|
||||
def new_handler() -> RichHandler:
|
||||
return RichHandler(
|
||||
console=console,
|
||||
highlighter=NullHighlighter(),
|
||||
markup=True,
|
||||
@@ -31,10 +63,24 @@ def create_handler() -> RichHandler:
|
||||
omit_repeated_times=False,
|
||||
log_time_format='%Y-%m-%d %I:%M:%S %p',
|
||||
)
|
||||
handler.setFormatter(AppNameFormatter(fmt='[appname]{appname}[/] {message}', style='{'))
|
||||
|
||||
|
||||
def setup_handler(**kwargs) -> RichHandler:
|
||||
handler = new_handler()
|
||||
handler.setFormatter(RoomControllerFormatter(**kwargs))
|
||||
return handler
|
||||
|
||||
|
||||
def setup_component_logging(self):
|
||||
typ = type(self).__name__
|
||||
logger = logging.getLogger(f'room_control.{self.args["app"]}')
|
||||
self.logger = logger.getChild(typ)
|
||||
if len(self.logger.handlers) == 0:
|
||||
self.logger.setLevel(logging.INFO)
|
||||
self.logger.addHandler(setup_handler(room=self.args["app"], component=typ))
|
||||
self.logger.propagate = False
|
||||
|
||||
|
||||
def init_logging(self: ADAPI, level):
|
||||
for h in logging.getLogger('AppDaemon').handlers:
|
||||
og_formatter = h.formatter
|
||||
@@ -45,7 +91,7 @@ def init_logging(self: ADAPI, level):
|
||||
if not any(isinstance(h, RichHandler) for h in self.logger.handlers):
|
||||
self.logger.propagate = False
|
||||
self.logger.setLevel(level)
|
||||
self.logger.addHandler(create_handler())
|
||||
self.logger.addHandler(setup_handler())
|
||||
self.log(f'Added rich handler for [bold green]{self.logger.name}[/]')
|
||||
# self.log(f'Formatter for RichHandler: {handler.formatter}')
|
||||
|
||||
|
||||
34
motion.py
34
motion.py
@@ -3,9 +3,9 @@ from datetime import timedelta
|
||||
|
||||
from appdaemon.entity import Entity
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from room_control import RoomController
|
||||
from console import setup_component_logging
|
||||
|
||||
from appdaemon import utils
|
||||
from room_control import RoomController
|
||||
|
||||
|
||||
class Motion(Hass):
|
||||
@@ -26,33 +26,32 @@ class Motion(Hass):
|
||||
return self.ref_entity.get_state() == 'on'
|
||||
|
||||
def initialize(self):
|
||||
setup_component_logging(self)
|
||||
self.app: RoomController = self.get_app(self.args['app'])
|
||||
self.log(f'Connected to app {self.app.name}')
|
||||
self.log(f'Connected to AD app [room]{self.app.name}[/]')
|
||||
|
||||
base_kwargs = dict(
|
||||
entity_id=self.ref_entity.entity_id,
|
||||
immediate=True, # avoids needing to sync the state
|
||||
immediate=True, # avoids needing to sync the state
|
||||
)
|
||||
# don't need to await these because they'll already get turned into a task by the utils.sync_wrapper decorator
|
||||
self.listen_state(**base_kwargs, attribute='brightness', callback=self.callback_light_on)
|
||||
self.listen_state(**base_kwargs, new='off', callback=self.callback_light_off)
|
||||
|
||||
def listen_motion_on(self):
|
||||
"""Sets up the motion on callback to activate the room
|
||||
"""
|
||||
"""Sets up the motion on callback to activate the room"""
|
||||
self.cancel_motion_callback()
|
||||
self.listen_state(
|
||||
callback=self.app.activate_all_off,
|
||||
entity_id=self.sensor.entity_id,
|
||||
new='on',
|
||||
oneshot=True,
|
||||
cause='motion on'
|
||||
cause='motion on',
|
||||
)
|
||||
self.log(f'Waiting for motion on {self.sensor.friendly_name}')
|
||||
|
||||
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"""
|
||||
self.cancel_motion_callback()
|
||||
self.listen_state(
|
||||
callback=self.app.deactivate,
|
||||
@@ -60,27 +59,24 @@ class Motion(Hass):
|
||||
new='off',
|
||||
duration=duration.total_seconds(),
|
||||
oneshot=True,
|
||||
cause='motion off'
|
||||
cause='motion off',
|
||||
)
|
||||
self.log(f'Waiting for motion to stop on {self.sensor.friendly_name} for {duration}')
|
||||
|
||||
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:
|
||||
self.log(f'{entity} turned on')
|
||||
duration = self.app.off_duration()
|
||||
self.listen_motion_off(duration)
|
||||
|
||||
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.listen_motion_on()
|
||||
|
||||
def get_app_callbacks(self, name: str = None):
|
||||
"""Gets all the callbacks associated with the app
|
||||
"""
|
||||
"""Gets all the callbacks associated with the app"""
|
||||
name = name or self.name
|
||||
callbacks = {
|
||||
handle: info
|
||||
@@ -92,16 +88,14 @@ class Motion(Hass):
|
||||
|
||||
def get_sensor_callbacks(self):
|
||||
return {
|
||||
handle: info
|
||||
for handle, info in self.get_app_callbacks().items()
|
||||
if info['entity'] == self.sensor.entity_id
|
||||
handle: info for handle, info in self.get_app_callbacks().items() if info['entity'] == self.sensor.entity_id
|
||||
}
|
||||
|
||||
def cancel_motion_callback(self):
|
||||
callbacks = self.get_sensor_callbacks()
|
||||
# self.log(f'Found {len(callbacks)} callbacks for {self.sensor.entity_id}')
|
||||
for handle, info in callbacks.items():
|
||||
entity = info["entity"]
|
||||
entity = info['entity']
|
||||
kwargs = info['kwargs']
|
||||
if (m := re.match('new=(?P<new>.*?)\s', kwargs)) is not None:
|
||||
new = m.group('new')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
@@ -9,7 +10,7 @@ from appdaemon.entity import Entity
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
||||
from astral import SunDirection
|
||||
from console import console, init_logging, deinit_logging
|
||||
from console import setup_handler
|
||||
from rich.table import Table
|
||||
|
||||
|
||||
@@ -21,6 +22,9 @@ def str_to_timedelta(input_str: str) -> datetime.timedelta:
|
||||
return datetime.timedelta()
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoomState:
|
||||
scene: Dict[str, Dict[str, str | int]]
|
||||
@@ -128,7 +132,6 @@ class RoomConfig:
|
||||
return state.off_duration
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class RoomController(Hass, Mqtt):
|
||||
"""Class for linking room's lights with a motion sensor.
|
||||
|
||||
@@ -139,8 +142,6 @@ class RoomController(Hass, Mqtt):
|
||||
- When the light comes on, check if it's attributes match what they should, given the time.
|
||||
"""
|
||||
|
||||
rich_logging: bool = False
|
||||
|
||||
@property
|
||||
def states(self) -> List[RoomState]:
|
||||
return self._room_config.states
|
||||
@@ -151,21 +152,20 @@ class RoomController(Hass, Mqtt):
|
||||
self._room_config.states = new
|
||||
|
||||
def initialize(self):
|
||||
if (level := self.args.get('rich', False)):
|
||||
init_logging(self, level)
|
||||
self.rich_logging = True
|
||||
|
||||
self.log(f'Initializing {self}')
|
||||
self.logger = logger.getChild(self.name)
|
||||
if not self.logger.hasHandlers():
|
||||
self.logger.setLevel(logging.INFO)
|
||||
self.logger.addHandler(setup_handler(room=self.name))
|
||||
# console.log(f'[yellow]Added RichHandler to {self.logger.name}[/]')
|
||||
|
||||
self.app_entities = self.gather_app_entities()
|
||||
# self.log(f'entities: {self.app_entities}')
|
||||
self.refresh_state_times()
|
||||
self.run_daily(callback=self.refresh_state_times, start='00:00:00')
|
||||
self.log(f'Initialized [bold green]{type(self).__name__}[/]')
|
||||
|
||||
def terminate(self):
|
||||
self.log('[bold red]Terminating[/]', level='DEBUG')
|
||||
deinit_logging(self)
|
||||
self.log('Success', level='DEBUG')
|
||||
|
||||
def gather_app_entities(self) -> List[str]:
|
||||
"""Returns a list of all the entities involved in any of the states"""
|
||||
@@ -209,9 +209,9 @@ class RoomController(Hass, Mqtt):
|
||||
|
||||
assert isinstance(state.time, datetime.time), f'Invalid time: {state.time}'
|
||||
|
||||
if self.rich_logging:
|
||||
table = self._room_config.rich_table(self.name)
|
||||
console.log(table, highlight=False)
|
||||
# if self.rich_logging:
|
||||
# table = self._room_config.rich_table(self.name)
|
||||
# console.log(table, highlight=False)
|
||||
|
||||
self.states = sorted(self.states, key=lambda s: s.time, reverse=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user