diff --git a/IoTuring/Entity/Deployments/Brightness/Brightness.py b/IoTuring/Entity/Deployments/Brightness/Brightness.py new file mode 100644 index 000000000..87fe2df40 --- /dev/null +++ b/IoTuring/Entity/Deployments/Brightness/Brightness.py @@ -0,0 +1,149 @@ +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 +import re +import os +import sys + + +KEY = 'brightness' +KEY_STATE = 'brightness_state' + +CONFIG_KEY_GPU = 'gpu' + +VALUEFORMATTER_OPTIONS_PERCENT = ValueFormatterOptions( + ValueFormatterOptions.TYPE_PERCENTAGE) + +class Brightness(Entity): + NAME = "Brightness" + ALLOW_MULTI_INSTANCE = True + + + def Initialize(self): + + self.RegisterEntitySensor( + EntitySensor(self, KEY_STATE, + supportsExtraAttributes=False, + valueFormatterOptions=VALUEFORMATTER_OPTIONS_PERCENT)) + + + + 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 + else: + self.Log(self.Logger.LOG_WARNING, + 'No brightness sensor available for this operating system') + + self.RegisterEntityCommand(EntityCommand(self, KEY, self.Callback, KEY_STATE)) + + 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)) + + + 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') + + 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 + 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}')] + + preset.AddEntry( + name="which GPUs backlight you want to control?", + key=CONFIG_KEY_GPU, + question_type='select', + choices=gpus + ) + return preset + + @classmethod + def CheckSystemSupport(cls): + 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: + raise Exception( + '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 ?') + elif OsD.IsLinux(): + if not os.path.exists('/sys/class/backlight'): #TODO check if this dir always exists + raise Exception( + 'Brightness not available, no backlight found in /sys/class/backlight') + else: + raise NotImplementedError('Brightness not available for this OS') \ No newline at end of file diff --git a/IoTuring/Entity/ToImplement/Brightness/Brightness.py b/IoTuring/Entity/ToImplement/Brightness/Brightness.py deleted file mode 100644 index d7c5d6c47..000000000 --- a/IoTuring/Entity/ToImplement/Brightness/Brightness.py +++ /dev/null @@ -1,170 +0,0 @@ -from IoTuring.Entity.Entity import Entity -import subprocess - -supports_win_brightness = True -try: - import wmi # Only to get windows brightness - import pythoncom -except: - supports_win_brightness = False - - -IN_TOPIC = 'brightness/set' # Receive a set message -OUT_TOPIC = 'brightness/get' # Send a message with the value - - -class Brightness(Entity): - def Initialize(self): - self.SubscribeToTopic(IN_TOPIC) - self.AddTopic(OUT_TOPIC) - self.stopCommand = False - self.stopSensor = False - self.stateOff = False - - def PostInitialize(self): - os = self.GetOS() - - # Sensor function settings - if(os == self.consts.FIXED_VALUE_OS_WINDOWS): - self.GetBrightness_OS = self.GetBrightness_Win - elif(os == self.consts.FIXED_VALUE_OS_MACOS): - self.GetBrightness_OS = self.GetBrightness_macOS - elif(os == self.consts.FIXED_VALUE_OS_LINUX): - self.GetBrightness_OS = self.GetBrightness_Linux - else: - self.Log(self.Logger.LOG_WARNING, - 'No brightness sensor available for this operating system') - self.stopSensor = True - - # Command function settings - if(os == self.consts.FIXED_VALUE_OS_WINDOWS): - self.SetBrightness_OS = self.SetBrightness_Win - elif(os == self.consts.FIXED_VALUE_OS_MACOS): - self.SetBrightness_OS = self.SetBrightness_macOS - elif(os == self.consts.FIXED_VALUE_OS_LINUX): - self.SetBrightness_OS = self.SetBrightness_Linux - else: - self.Log(self.Logger.LOG_WARNING, - 'No brightness command available for this operating system') - self.stopCommand = True - - - def Callback(self, message): - state = message.payload.decode("utf-8") - if not self.stopCommand: - - if state == self.consts.ON_STATE and self.stateOff is not False: - state = self.stateOff if self.stateOff is not None else 100 - - if state == self.consts.OFF_STATE: - self.stateOff = self.GetTopicValue(OUT_TOPIC) - state = 1 - elif self.stateOff is not False: - self.stateOff = False - - try: - # Value from 0 and 100 - self.SetBrightness_OS(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)) - - # Finally, tell the sensor to update and to send - self.CallUpdate() - self.SendOnlineState() - self.lastSendingTime = None # Force sensor to send immediately - - def Update(self): - if not self.stopSensor: - self.SetTopicValue(OUT_TOPIC, self.GetBrightness_OS(), - self.ValueFormatter.TYPE_PERCENTAGE) - self.SendOnlineState() - - def SetBrightness_macOS(self, value): - 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): - command = 'xbacklight -set ' + str(value) - subprocess.Popen(command.split(), stdout=subprocess.PIPE) - - def SetBrightness_Win(self, value): - if supports_win_brightness: - pythoncom.CoInitialize() - return wmi.WMI(namespace='wmi').WmiMonitorBrightnessMethods()[0].WmiSetBrightness(value, 0) - else: - raise Exception( - 'No WMI module installed') - - def GetBrightness_macOS(self): - 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): - try: - command = 'xbacklight' - process = subprocess.Popen(command.split(), stdout=subprocess.PIPE) - stdout = process.communicate()[0] - brightness = float(stdout) - return brightness - except: - raise Exception( - 'You sure you installed Brightness from Homebrew ? (else try checking you PATH)') - - def GetBrightness_Win(self): - if supports_win_brightness: - return int(wmi.WMI(namespace='wmi').WmiMonitorBrightness() - [0].CurrentBrightness) - else: - raise Exception( - 'No WMI module installed') - - def GetOS(self): - # Get OS from OsSensor and get temperature based on the os - os = self.FindEntity('Os') - if os: - if not os.postinitializeState: # I run this function in post initialize so the os sensor might not be ready - os.CallPostInitialize() - os.CallUpdate() - return os.GetTopicValue() - - def ManageDiscoveryData(self, discovery_data): - for data in discovery_data: - data['expire_after']="" - - self.SendOnlineState() - - discovery_data[0]['payload']['brightness_state_topic'] = self.SelectTopic( - OUT_TOPIC) - discovery_data[0]['payload']['state_topic'] = self.SelectTopic( - self.STATE_TOPIC) - discovery_data[0]['payload']['brightness_command_topic'] = self.SelectTopic( - IN_TOPIC) - discovery_data[0]['payload']['command_topic'] = self.SelectTopic( - IN_TOPIC) - discovery_data[0]['payload']['payload_on'] = self.consts.ON_STATE - discovery_data[0]['payload']['payload_off'] = self.consts.OFF_STATE - discovery_data[0]['payload']['brightness_scale'] = 100 - - return discovery_data - - STATE_TOPIC = 'brightness/state' - - def SendOnlineState(self): - if self.GetTopicValue(OUT_TOPIC) and int(self.GetTopicValue(OUT_TOPIC)) > 1: - self.mqtt_client.SendTopicData( - self.SelectTopic(self.STATE_TOPIC), self.consts.ON_STATE) - else: - self.mqtt_client.SendTopicData( - self.SelectTopic(self.STATE_TOPIC), self.consts.OFF_STATE) diff --git a/IoTuring/Entity/ToImplement/Brightness/settings.yaml b/IoTuring/Entity/ToImplement/Brightness/settings.yaml deleted file mode 100644 index df09aa287..000000000 --- a/IoTuring/Entity/ToImplement/Brightness/settings.yaml +++ /dev/null @@ -1,16 +0,0 @@ -requirements: - sensors: - - Os: - dont_send: True - -discovery: - homeassistant: # Must match discovery preset name - - topic: "brightness/get" - disable: True - payload: - name: "Brightness level" - unit_of_measurement: "%" - - topic: "brightness/set" - type: "light" - payload: - name: "Brightness" \ No newline at end of file diff --git a/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/entities.yaml b/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/entities.yaml index c5afe11e0..d1e50db1c 100644 --- a/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/entities.yaml +++ b/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/entities.yaml @@ -124,3 +124,8 @@ TerminalButton: name: Terminal Button icon: mdi:console custom_type: button +Brightness: + name: Display Brightness + unit_of_measurement: "%" + icon: mdi:brightness-6 + custom_type: number