245 lines
8.2 KiB
Python
245 lines
8.2 KiB
Python
import aiofiles
|
|
import asyncio
|
|
import enum
|
|
import evdev
|
|
import logging
|
|
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 BacklightDevice:
|
|
logger: logging.Logger = None
|
|
path: str = ""
|
|
max_brightness: int = -1
|
|
|
|
def __init__(self, device, logger):
|
|
self.path = f"/sys/class/backlight/{device}"
|
|
self.logger = logger.getChild("backlight")
|
|
|
|
async def get(self) -> float:
|
|
if self.max_brightness == -1:
|
|
await self._load_max_brightness()
|
|
|
|
rv = await self.get_raw()
|
|
return rv / self.max_brightness * 100
|
|
|
|
async def get_raw(self) -> int:
|
|
async with aiofiles.open(f"{self.path}/brightness", "r") as file:
|
|
val = await file.read()
|
|
return int(val.rstrip())
|
|
|
|
async def set(self, new_value: float):
|
|
if self.max_brightness == -1:
|
|
await self._load_max_brightness()
|
|
|
|
nv = int(new_value * self.max_brightness / 100)
|
|
await self.set_raw(nv)
|
|
|
|
async def set_raw(self, new_value: int):
|
|
self.logger.debug(f"writing {new_value} to {self.path}/brightness")
|
|
async with aiofiles.open(f"{self.path}/brightness", "w") as f:
|
|
try:
|
|
await f.write(str(new_value))
|
|
except PermissionError:
|
|
self.logger.error(
|
|
f"cannot write new backlight value to {self.path}: permission error"
|
|
)
|
|
|
|
async def _load_max_brightness(self):
|
|
async with aiofiles.open(f"{self.path}/max_brightness", "r") as file:
|
|
val = await file.read()
|
|
self.max_brightness = int(val.rstrip())
|
|
|
|
|
|
class DisplayBrightnessManager:
|
|
last_input = time.time()
|
|
bd: BacklightDevice = None
|
|
brightness_mode: BrightnessMode = BrightnessMode.MODE_AUTO
|
|
brightness_mode_new: BrightnessMode = 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,
|
|
brightness_device,
|
|
touch_device,
|
|
logger: logging.Logger,
|
|
):
|
|
self.logger = logger
|
|
self.bd = BacklightDevice(brightness_device, 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.mqtt_device,
|
|
unique_id=f"{device.device_id}_force_display_on",
|
|
)
|
|
switch_settings = ha_mqtt_discoverable.Settings(
|
|
mqtt=device.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.bd.get()
|
|
|
|
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 self.bd.set(new_val)
|
|
|
|
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 self.bd.set(50)
|
|
|
|
async def brightness_loop(self):
|
|
self.logger.info("starting brightness loop")
|
|
self.brightness_value = await self.bd.get()
|
|
|
|
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)
|