ha-kiosk-agent/src/display.py

176 lines
6.7 KiB
Python

import asyncio
import enum
import evdev
import logging
import subprocess
import time
import ha_mqtt_discoverable
from paho.mqtt.client import Client, MQTTMessage
class BrightnessMode(enum.Enum):
MODE_MAN = 0
MODE_AUTO = 1
class BrightnessLevel(enum.Enum):
LEVEL_OFF = 0
LEVEL_DIM = 1
LEVEL_BRIGHT = 2
class DisplayBrightnessManager():
last_input = time.time()
brightness_mode = BrightnessMode.MODE_AUTO
brightness_mode_new = BrightnessMode.MODE_AUTO
brightness_level = BrightnessLevel.LEVEL_BRIGHT
brightness_value = 0
brightness_value_new = 50
mqtt_entity = None
display_device = None
display_switched_off = False
touch_device = None
logger = None
def __init__(self, device, display_device, touch_device, logger: logging.Logger):
self.logger = logger
self.display_device = display_device
self.touch_device = self.get_touch_device(touch_device)
force_display_on_info = ha_mqtt_discoverable.sensors.SwitchInfo(
name="Force Display on",
device=device.get_mqtt_device(),
unique_id=f'{device.get_device_id()}_force_display_on')
switch_settings = ha_mqtt_discoverable.Settings(mqtt=device.get_mqtt_settings(), entity=force_display_on_info, manual_availability=True)
self.mqtt_entity = ha_mqtt_discoverable.sensors.Switch(switch_settings, self.switch_callback)
self.mqtt_entity.set_availability(True)
def get_touch_device(self, touch_device) -> evdev.InputDevice:
if touch_device is None:
return None
if '/dev/input' in touch_device:
return evdev.InputDevice(touch_device)
for dev in evdev.list_devices():
d = evdev.InputDevice(dev)
if touch_device in d.name:
self.logger.info(f'using {dev} as input device')
return d
self.logger.error(f'no input device found matching {touch_device}')
return None
def switch_callback(self, client: Client, user_data, message: MQTTMessage):
payload = message.payload.decode()
if payload == "ON":
self.set_mode(BrightnessMode.MODE_MAN)
self.mqtt_entity.on()
elif payload == "OFF":
self.set_mode(BrightnessMode.MODE_AUTO)
self.mqtt_entity.off()
def get_brightness_value(self) -> float:
return self.brightness_value
def set_brightness_value(self, number: float):
self.brightness_value_new = number
def set_mode(self, mode: BrightnessMode):
self.brightness_mode_new = mode
logging.info(f'brightness mode: {self.brightness_mode}, new mode: {self.brightness_mode_new}')
async def switch_display_on(self):
self.logger.info('switching screen on')
self.display_switched_off = False
await asyncio.create_subprocess_exec('xset', '-display', self.display_device, 'dpms', 'force', 'on')
async def switch_display_off(self):
self.logger.info('switching screen off')
self.display_switched_off = True
await asyncio.create_subprocess_exec('xset', '-display', self.display_device, 'dpms', 'force', 'off')
async def input_device_loop(self):
async for ev in self.touch_device.async_read_loop():
now = time.time()
if self.last_input + 5 >= now:
continue
self.last_input = now
async def dim_screen(self, to_value):
self.logger.debug(f'dimming screen to {to_value}')
self.brightness_value = to_value
self.brightness_value_new = to_value
from_value = 0
if self.brightness_level == BrightnessLevel.LEVEL_DIM:
from_value = 5
elif self.brightness_level == BrightnessLevel.LEVEL_BRIGHT:
from_value = 50
if to_value != 0 and self.display_switched_off:
await self.switch_display_on()
return
self.logger.info(f'dimming screen from {from_value} to {to_value}')
step_val=(to_value-from_value)/20
last_val = from_value
for i in range(1, 20):
new_val = "{:.0f}".format(from_value + i*step_val)
if new_val != last_val:
await asyncio.create_subprocess_exec('light', '-S', f'{new_val}%', stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL)
await asyncio.sleep(0.005)
last_val = new_val
if to_value == 0:
await self.switch_display_off()
await asyncio.sleep(0.1)
self.logger.info(f'setting initial brightness to 50%')
await asyncio.create_subprocess_exec('light', '-S', '50%', stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL)
async def brightness_loop(self):
self.logger.info('starting brightness loop')
res = subprocess.run(['light', '-G'], capture_output=True)
self.brightness_value = float(res.stdout.decode('utf-8'))
while True:
if self.brightness_value != self.brightness_value_new:
await self.dim_screen(self.brightness_value_new)
if self.brightness_mode != self.brightness_mode_new:
self.logger.info(f'brightness mode changed to {self.brightness_mode_new}')
if self.brightness_mode_new == BrightnessMode.MODE_MAN:
await self.dim_screen(50)
self.brightness_level = BrightnessLevel.LEVEL_BRIGHT
else:
self.last_input = time.time()
self.brightness_mode = self.brightness_mode_new
if self.brightness_mode == BrightnessMode.MODE_AUTO:
self.logger.debug(f'brightness loop begin: {self.last_input}, {time.time()}')
if self.last_input + 30 < time.time():
if self.brightness_level != BrightnessLevel.LEVEL_OFF:
await self.dim_screen(0)
self.brightness_level = BrightnessLevel.LEVEL_OFF
elif self.last_input + 15 < time.time():
if self.brightness_level != BrightnessLevel.LEVEL_DIM:
await self.dim_screen(5)
self.brightness_level = BrightnessLevel.LEVEL_DIM
elif self.brightness_level != BrightnessLevel.LEVEL_BRIGHT:
await self.dim_screen(50)
self.brightness_level = BrightnessLevel.LEVEL_BRIGHT
await asyncio.sleep(0.05)
async def run(self):
tasks = [self.brightness_loop()]
if self.touch_device:
tasks.append(self.input_device_loop())
else:
self.logger.warn('not starting display event loop: no touch device set')
asyncio.gather(*tasks)