Emulated_hue switches brightness instantly to 100%

After a recent update my emulated_hue did not work anymore as before.

I made it work again but then found out that on each turn_on over emulated_hue
the light

  • first starts with the previous brightness, (eg 10%)
  • then switches instantly to 100%.

Over the dashboard everything is fine, only when I use the activation over
emulated_hue I see a instant “brightness” : 255 change after I “turn_on”.

I haven’t found any reports about this, most likely it’s only in my setup?
I was unable to find a workaround, so I started an AppDaemon script.

My idea was to block the call to brightness = 255, for a while (eg 4 secs),
directly after turn_on.

I haven’t figured out what component in my setup causes this behavior.

AppDaemon Script attached.

# filename: intercept.py
#
import appdaemon.plugins.hass.hassapi as hass
import datetime
import appdaemon.plugins.mqtt.mqttapi as mqtt
import time

# This class creates a virtual light which the only purpose to 
# block any brightness calls for 4 seconds after a "turn_on" call.
#
# On my emulated_hue the lights are turned on with default brightness (eg. 10%)
# and directly afterwards the brighness is increased to 100%.
#
# This class intercepts the emulalted_hue calls, it creates a virtual light
# and forwards the calls to the real light(group). Calls after a turn_on
# are blocked for N seconds.

# Setup:
# Assume there is a "GroupKitchen" in configuration.yaml for emulated hue.
#
# [...]
# emulated_hue:
#   listen_port: 80
#   expose_by_default: false
#   entities:
#     light.groupkitchen:      # <-- Your exact entity ID in Home Assistant
#       name: "Kitchen"        # <-- The name Alexa/Google Home will see
#       hidden: false

# This needs to be changed to 
# [...]
# emulated_hue:
#   listen_port: 80
#   expose_by_default: false
#   entities:
#     light.virtual_groupkitchen:
#     name: "Kitchen"
#     hidden: false
#


# In AppDaemon there is for every HUE light an intercepted light class.
# For example:
# - GroupKitchen it will have a module instance intercept_GroupKitchen.
#
# intercept_GroupKitchen:    
#   module: intercept    
#   class: InterceptHue     
#   
#
# Adding another light group is then done by adding new module intercept_xyz in 
# apps.yaml
# GroupKichten has to match the real light.groupkitchen entity.



# Testing:
#
# Start with one Light "groupkitchen" and make the hue / appdaemon changes.
#
# - check in Settings->Developer Tools -> check Configuration etc..
# - reboot
# - check if virtual_groupkitchen is available in the emulated hue:
#   http://<homeassistant.lan>/api/v2/lights
# - the homeassisant dashboard should have an entity virtual groupkitchen
# 
# If the dashboard works for virtual_groupkitchen (on/off brightness), 
# the hue lights needs to be re-discovered, because the uniqueids have changed. 
# (even if the name stacs the same) Remove the old lights, then rediscover.


class InterceptHue(hass.Hass,mqtt.Mqtt):

    def initialize(self):
        self.bright_pct=100
        self.blockInSec=4
        self.blocktime=time.time()

        # get the entity_id of the real light group 
        # get from our instance name "intercept_GroupKitchen" -> GroupKitchen (LightGroup)
        # This name (entity_id or friendly_name) must macht exactly
        self.lightGroup=self.name.replace('intercept_', '').lower()
        self.entity_id_group = "light."+self.lightGroup
        # create entity_id, which is always lower case
        self.entity_id_virtual="light.virtual_"+self.lightGroup
        self.log(f"creating entity_id : {self.entity_id_virtual} from entitiy_id : {self.entity_id_group}")
        self.init_virtual_light_state()     
        
        # Note: here entity_id will be an array: [ "light.name"]
        self.listen_event(self.my_call_back, "call_service", domain="light" )
        # This filter will not work:  service_data = { "entity_id" : [ self.entity_id_virtual ]})

        # Note: here entity_id is a string: "light.name"
        self.listen_event(self.turn_on_listener, "call_service", service="turn_on",
                        domain="light",
                        service_data={"entity_id": self.entity_id_virtual})
        self.listen_event(self.turn_off_listener, "call_service", service="turn_off", 
                        domaine="light",
                        service_data={"entity_id": self.entity_id_virtual})
        self.listen_state(self.light_state_changed,self.entity_id_group)

    def calc_percent(self,brightness_val):
        brightness_pct = int(100.0* (float(brightness_val)/255.0))
        return brightness_pct


    def init_virtual_light_state(self):
        self.log(f"ENTER: init_virtual_light_state entity_id: {self.entity_id_group}")
        cur_state=self.get_state(self.entity_id_group)
        if not (cur_state in ["on","off"]):
            self.log(f"{self.entity_id_group} not initialized state={cur_state}")
            cur_state="off"
            
            
        stateAllTmpGroup = self.get_state(self.entity_id_group, attribute="all")
        self.log(f"INFO: init_virtual_light_state {stateAllTmpGroup}")
        brightness=stateAllTmpGroup.get("attributes").get("brightness")
        if (brightness != None):
            self.bright_pct=self.calc_percent(brightness)

        # create a usual light entity with brightness:
        attrib = {  'min_color_temp_kelvin': 2000, 'max_color_temp_kelvin': 6535, 
                    'supported_color_modes': ['color_temp', 'rgb'], 
                    'color_mode': 'color_temp',
                    'brightness': brightness,
                    'color_temp_kelvin': 2702, 
                    'hs_color': [28.391, 65.659], 
                    'rgb_color': [255, 167, 88], 
                    'xy_color': [0.524, 0.388], 
                    'supported_features': 40 }
        
        
        self.log(f"INIT: {self.entity_id_virtual} {attrib}")
        self.set_state(self.entity_id_virtual,state=cur_state,attributes=attrib)


    def light_state_changed(self, entity, attribute, old, new, kwargs):
        self.log(f"ENTER: light_state_changed {entity} from {old} to {new}")
        attrs = self.get_state(entity, attribute="all")
        #self.log(f"attrs: {attrs}")
        virtual_state=self.get_state(self.entity_id_virtual)
        if (virtual_state == new):
            self.log(f"IGNORE: light_state_changed new {new} already in {self.entity_id_virtual}")
            return
        if (new == "on"):
            self.turn_on(self.entity_id_virtual)
        else:
            self.turn_off(self.entity_id_virtual)

    def set_virtual_light_state(self, new):
        
        self.log(f"ENTER: set_virtual_light_state {self.entity_id_virtual} {new} brightness_pct={self.bright_pct}")
        self.set_state(self.entity_id_virtual, state=new)

        group_state=self.get_state(self.entity_id_group)
        if (group_state == new):
            self.log(f"IGNORE: set_virtual_light_state new {new} already in {self.entity_id_virtual}")
            return
        
        self.call_service("light/turn_"+new, entity_id=self.entity_id_group,
                        transition=0)
        

    def my_call_back(self, event_name, data, **kwargs):
        # event_name will be 'call_service'
        
        # Extract details about the service call
        domain = data.get("domain")
        service = data.get("service")
        service_data = data.get("service_data", {})
        target_entity = service_data.get("entity_id")

        lFound=False
        self.log(f"ENTER: my_call_back : {domain}.{service} on {data} ")
        if (type(target_entity) is list):
            cnt=0
            for element in target_entity:
                #self.log(f"Array compare {target_entity}[{cnt}] == {element}")
                if (element == self.entity_id_virtual):
                    target_entity=self.entity_id_virtual
                    lFound=True
                    #self.log(f"FOUND !")
                    break
        if (type(target_entity) is str):
                if (target_entity == self.entity_id_virtual):
                    lFound=True

        if (lFound == False):
            entity=service_data.get("entity_id")
            self.log(f"IGNORE: my_call_back {entity} ")
            return
        # 
        brightness_pct = service_data.get("brightness_pct",None)
        if (brightness_pct == None):
            brightness_val = service_data.get("brightness",None)
            if (brightness_val != None):
                brightness_pct = self.calc_percent(brightness_val)
                #self.log(f"INFO: my_call_back: {target_entity}.{service} on brightness_calc={brightness_pct} ")
        if (service== "turn_on") and (brightness_pct != None):
            if (time.time() < self.blocktime) :
                self.log(f"BLOCK my_call_back: {target_entity}.{service} on brightness_ptc={brightness_pct} (keep : {self.bright_pct})")
            else:
                self.bright_pct=brightness_pct
            self.log(f"OK my_call_back: {target_entity}.{service} on brightness_ptc={self.bright_pct} ")
            self.turn_on(self.entity_id_group, brightness_pct=self.bright_pct)
            return
        self.log(f"INFO my_call_back: {target_entity}.{service} on brightness_pct={self.bright_pct} ")
        if (service== "turn_on"):
            self.log(f"OK: my_call_back: {service} {target_entity}")
            self.blocktime=time.time()+self.blockInSec
            self.set_virtual_light_state("on")
            #self.turn_on(self.entity_id_virtual)
            return            
        if (service== "turn_off"):
            self.log(f"OK: my_call_back: {service} {target_entity}")
            self.set_virtual_light_state("off")
            #self.turn_off(self.entity_id_group)
            return
        self.log(f"IGNORE: my_call_back: {service} {target_entity} brightness_pct={brightness_pct}")
        


    

    def turn_on_listener(self, event_name, data, kwargs):
        self.log(f"ENTER: turn_on_listener {self.entity_id_virtual} {data} ")

        self.set_virtual_light_state("on")
        #self.turn_on(self.entity_id_virtual)
        

    def turn_off_listener(self, event_name, data, kwargs):
        self.log(f"ENTER: turn_off_listener {self.entity_id_virtual} {data}")
        self.set_virtual_light_state("off")
        #self.turn_off(self.entity_id_virtual)