Skip to content

Commit

Permalink
Brightness, multi sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
infeeeee committed Sep 2, 2024
1 parent cb92d18 commit f8057fa
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 196 deletions.
288 changes: 180 additions & 108 deletions IoTuring/Entity/Deployments/Brightness/Brightness.py
Original file line number Diff line number Diff line change
@@ -1,149 +1,221 @@
from IoTuring.Entity.Entity import Entity
from pathlib import Path
from IoTuring.Entity.Entity import Entity
from IoTuring.Configurator.MenuPreset import MenuPreset
from IoTuring.Entity.EntityData import EntitySensor, EntityCommand
from IoTuring.MyApp.SystemConsts.OperatingSystemDetection import OperatingSystemDetection as OsD
from IoTuring.Entity.ValueFormat import ValueFormatterOptions
import subprocess
from IoTuring.MyApp.SystemConsts import OperatingSystemDetection as OsD
from IoTuring.MyApp.SystemConsts import DesktopEnvironmentDetection as De
from IoTuring.Entity.ValueFormat import ValueFormatterOptions


import re
import os
import sys


KEY = 'brightness'
KEY_STATE = 'brightness_state'
KEY_CMD = "command"
KEY_STATE = "state"
KEY_BRIGHTNESS = "value"

CONFIG_KEY_GPU = "gpu"


class BrightnessCmds:
def __init__(
self,
scale: float = 100,
decimals: int = 0,
set_command: str = "{}",
get_command: str = "",
) -> None:
self.scale = scale
self.decimals = decimals
self.set_command = set_command
self.get_command = get_command

def Set(self, value: int) -> None:
if not 0 <= value <= 255:
raise Exception("Invalid value")
scaled_value = (value / 255) * self.scale
scaled_value = round(scaled_value, self.decimals)
scaled_value = int(scaled_value) if self.decimals == 0 else scaled_value
self._SetValue(value_str=str(scaled_value))

def _SetValue(self, value_str: str) -> None:
command = self.set_command.format(value_str)
OsD.RunCommand(command, shell=True)

def Get(self) -> int:
scaled_value = float(self._GetValue())
value = (scaled_value / self.scale) * 255
return int(value)

def _GetValue(self) -> str:
value_str = OsD.RunCommand(self.get_command).stdout
return value_str


class Brightness_Macos(BrightnessCmds):
def __init__(self) -> None:
super().__init__(
scale=1,
decimals=2,
set_command="brightness {}",
get_command="brightness -l",
)

def _GetValue(self) -> str:
stdout = super()._GetValue()
brightness = re.findall("display 0: brightness.*$", stdout)[0][22:30]
return brightness


class Brightness_Win(BrightnessCmds):
def __init__(self, monitor_id: int = 0) -> None:
super().__init__()

import pythoncom
import wmi

pythoncom.CoInitialize()
self.monitor_id = monitor_id
self.wmi = wmi.WMI(namespace="wmi")

def _SetValue(self, value_str: str) -> None:
self.wmi.WmiMonitorBrightnessMethods()[self.monitor_id].WmiSetBrightness(
int(value_str), 0
)

def _GetValue(self) -> str:
return self.wmi.WmiMonitorBrightness()[self.monitor_id].CurrentBrightness

CONFIG_KEY_GPU = 'gpu'

VALUEFORMATTER_OPTIONS_PERCENT = ValueFormatterOptions(
ValueFormatterOptions.TYPE_PERCENTAGE)
class Brightness_Linux_ACPI(BrightnessCmds):
def __init__(self, configuredGPU: str) -> None:
self.gpu_path = Path(f"/sys/class/backlight/{configuredGPU}")
scale = int(self.get_from_file("max_brightness"))
super().__init__(scale=scale)

def _SetValue(self, value_str: str) -> None:
with open(self.gpu_path.joinpath("brightness"), "w") as file:
file.write(f"{value_str}\n")

def _GetValue(self) -> str:
return self.get_from_file("brightness")

def get_from_file(self, file_name: str) -> str:
with open(self.gpu_path.joinpath(file_name), "r") as file:
content = file.read()
return content.strip("\n")


class Brightness_Linux_Gnome(BrightnessCmds):
def __init__(self) -> None:
dbus_command = " ".join(
[
"gdbus call --session",
"--dest org.gnome.SettingsDaemon.Power",
"--object-path /org/gnome/SettingsDaemon/Power",
"--method org.freedesktop.DBus.Properties.{}",
"org.gnome.SettingsDaemon.Power.Screen Brightness",
]
)

super().__init__(
set_command=dbus_command.format("Set") + ' "<int32 {}>"',
get_command=dbus_command.format("Get"),
)

def _GetValue(self) -> str:
stdout = super()._GetValue()
brightness = re.findall(r"<(\d*)", stdout)[0]
return brightness


class Brightness(Entity):
NAME = "Brightness"
ALLOW_MULTI_INSTANCE = True

brightness_cmds: BrightnessCmds

def Initialize(self):

def Initialize(self):
self.RegisterEntitySensor(
EntitySensor(self, KEY_STATE,
supportsExtraAttributes=False,
valueFormatterOptions=VALUEFORMATTER_OPTIONS_PERCENT))



EntitySensor(
self,
KEY_STATE,
valueFormatterOptions=ValueFormatterOptions(
value_type=ValueFormatterOptions.TYPE_BINARY
),
)
)
self.RegisterEntitySensor(EntitySensor(self, KEY_BRIGHTNESS))
self.RegisterEntityCommand(
EntityCommand(
self,
KEY_CMD,
self.Callback,
connectedEntitySensorKeys=[KEY_STATE, KEY_BRIGHTNESS],
)
)

if OsD.IsWindows():
self.specificGetBrightness = self.GetBrightness_Win
self.specificSetBrightness = self.SetBrightness_Win
import wmi
import pythoncom
if OsD.IsMacos():
self.specificGetBrightness = self.GetBrightness_macOS
self.specificSetBrightness = self.SetBrightness_macOS
if OsD.IsLinux():
self.configuredGPU: str = self.GetFromConfigurations(CONFIG_KEY_GPU)
self.specificGetBrightness = self.GetBrightness_Linux
self.specificSetBrightness = self.SetBrightness_Linux
self.brightness_cmds = Brightness_Win()
elif OsD.IsMacos():
self.brightness_cmds = Brightness_Macos()
elif OsD.IsLinux():
if De.GetDesktopEnvironment() == "gnome":
self.brightness_cmds = Brightness_Linux_Gnome()
else:
configuredGPU: str = self.GetFromConfigurations(CONFIG_KEY_GPU)
self.brightness_cmds = Brightness_Linux_ACPI(configuredGPU)
else:
self.Log(self.Logger.LOG_WARNING,
'No brightness sensor available for this operating system')

self.RegisterEntityCommand(EntityCommand(self, KEY, self.Callback, KEY_STATE))
raise Exception("Unsupported OS!")

def Callback(self, message):
state = message.payload.decode("utf-8")
try:
# Value from 0 and 100
self.specificSetBrightness(int(state))
except ValueError: # Not int -> not a message for that function
return
except Exception as e:
raise Exception("Error during brightness set: " + str(e))

self.brightness_cmds.Set(int(state))

def Update(self):
brightness = self.specificGetBrightness()
self.SetEntitySensorValue(KEY_STATE, brightness)

def SetBrightness_macOS(self, value: str|int):
value = value/100 # cause I need it from 0 to 1
command = 'brightness ' + str(value)
subprocess.Popen(command.split(), stdout=subprocess.PIPE)

def SetBrightness_Linux(self, value):
# use acpi to controll backlight
with open(f'/sys/class/backlight/{self.configuredGPU}/brightness', 'w') as file:
file.write(f'{str(value)}\n')
value = self.brightness_cmds.Get()
self.SetEntitySensorValue(KEY_BRIGHTNESS, value)
self.SetEntitySensorValue(KEY_STATE, 1 if value > 0 else 0)

def SetBrightness_Win(self, value):
pythoncom.CoInitialize()
return wmi.WMI(namespace='wmi').WmiMonitorBrightnessMethods()[0].WmiSetBrightness(value, 0)

def GetBrightness_macOS(self) -> float:
try:
command = 'brightness -l'
process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
stdout = process.communicate()[0]
brightness = re.findall(
'display 0: brightness.*$', str(stdout))[0][22:30]
brightness = float(brightness)*100 # is between 0 and 1
return brightness
except:
raise Exception(
'You sure you installed Brightness from Homebrew ? (else try checking you PATH)')

def GetBrightness_Linux(self) -> int:
# get the content of the file /sys/class/backlight/intel_backlight/brightness
with open(f'/sys/class/backlight/{self.configuredGPU}/brightness', 'r') as file:
content = file.read()
brightness = int(content.strip('\n'))
return self.ConvertBrightness(brightness, from_scale=255, to_scale=100)

def GetBrightness_Win(self) -> int:
return int(wmi.WMI(namespace='wmi').WmiMonitorBrightness()[0].CurrentBrightness)

def ConvertBrightness(self, value, from_scale=255, to_scale=100) -> int:
"""Function to convert brightness values from one scale to another.
Args:
value (int): The brightness value to convert.
from_scale (int): The original scale of the brightness value. Default is 255.
to_scale (int): The target scale of the brightness value. Default is 100.
Returns:
float: The converted brightness value.
"""
return int((value / from_scale) * to_scale)


@classmethod
@classmethod
def ConfigurationPreset(cls):
preset = MenuPreset()
if OsD.IsLinux():
# find all GPUs in /sys/class/backlight by listing all directories
gpus = [gpu for gpu in os.listdir('/sys/class/backlight') if os.path.isdir(f'/sys/class/backlight/{gpu}')]
gpus = [
gpu
for gpu in os.listdir("/sys/class/backlight")
if os.path.isdir(f"/sys/class/backlight/{gpu}")
]

preset.AddEntry(
name="which GPUs backlight you want to control?",
key=CONFIG_KEY_GPU,
question_type='select',
choices=gpus
)
question_type="select",
choices=gpus,
)
return preset

@classmethod
def CheckSystemSupport(cls):
if OsD.IsWindows(): #TODO needs to be tested
if OsD.IsWindows(): # TODO needs to be tested
# if wmi and pythoncom are not available, raise an exception
if ['wmi', 'pythoncom'] not in sys.modules:
if ["wmi", "pythoncom"] not in sys.modules:
raise Exception(
'Brightness not available, have you installed \'wmi\' on pip ?')
elif OsD.IsMacos(): #TODO needs to be tested
if not OsD.CommandExists('brightness'):
"Brightness not available, have you installed 'wmi' on pip ?"
)
elif OsD.IsMacos(): # TODO needs to be tested
if not OsD.CommandExists("brightness"):
raise Exception(
'Brightness not avaidlable, have you installed \'brightness\' on Homebrew ?')
"Brightness not avaidlable, have you installed 'brightness' on Homebrew ?"
)
elif OsD.IsLinux():
if not os.path.exists('/sys/class/backlight'): #TODO check if this dir always exists
if not Path("/sys/class/backlight").exists():
# TODO check if this dir always exists
raise Exception(
'Brightness not available, no backlight found in /sys/class/backlight')
"Brightness not available, no backlight found in /sys/class/backlight"
)
else:
raise NotImplementedError('Brightness not available for this OS')
raise cls.UnsupportedOsException()
15 changes: 9 additions & 6 deletions IoTuring/Entity/Entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

class Entity(ConfiguratorObject, LogObject):

entitySensors: list[EntitySensor]
entityCommands: list[EntityCommand]

def __init__(self, single_configuration: SingleConfiguration) -> None:
super().__init__(single_configuration)

Expand Down Expand Up @@ -128,11 +131,11 @@ def GetAllEntityData(self) -> list:
""" safe - Return list of entity sensors and commands """
return self.entityCommands.copy() + self.entitySensors.copy() # Safe return: nobody outside can change the callback !

def GetAllUnconnectedEntityData(self) -> list[EntityData]:
""" safe - Return All EntityCommands and EntitySensors without connected command """
connected_sensors = [command.GetConnectedEntitySensor()
for command in self.entityCommands
if command.SupportsState()]
def GetAllUnconnectedEntityData(self) -> list[EntityCommand|EntitySensor]:
""" safe - Return All EntityCommands and EntitySensors without connected sensors """
connected_sensors = []
for command in self.entityCommands:
connected_sensors.extend(command.GetConnectedEntitySensors())
unconnected_sensors = [sensor for sensor in self.entitySensors
if sensor not in connected_sensors]
return self.entityCommands.copy() + unconnected_sensors.copy()
Expand Down Expand Up @@ -203,7 +206,7 @@ def RunCommand(self,
if p.stderr:
self.Log(error_loglevel,
f"Error during {command_name} command: {p.stderr}")

return p

except Exception as e:
Expand Down
Loading

0 comments on commit f8057fa

Please sign in to comment.