Python embedded code in a dictionary

Right now I have an app that uses a dictionary to determine what target entities to turn on/off based on a trigger entity being turned on or off. I’m being vague because triggers aren’t always switches, or light’s, sometimes they are media_players, input_booleans, or binary_sensors.

It all works pretty well. When I open the door to my office, the office_door trigger is found and as a results of it being opened, it turns on the lights in my office. When the door is closed, based on the same rule, but in response to the close state, it turns off the lights.

Now though, I want to make it react to non on/off triggers, like a trigger based on temperature range. To do this I’m thinking about switching from the on/off paradigm to a pass/fail or true/false paradigm. But then I need to be able to have python either execute a comparison embedded in the dictionary, or possibly execute an external (but still in AD) application.

What would be the best way to approach that?

i would probably put the comparison in the dictionary like:

{“light.office”:“on”,“sensor.office”:">25 and <28",“mediaplayer.home”:“off”}
and then a function for every way i want to compare things.

Instead of doing building in a calculator of sorts, is there a way to do an eval or something and have python interpret it as dynamic code or something?

You can have a function as a value in a dictionary. I would suggest you call functions from your dictionary, as opposed to doing logic within a single value.

Can you give an example of what you’re looking to do for the temperature range? It’s hard to understand what you’re getting at.

It’s hard for me to explain it as well without rambling. :slight_smile:

Here is what my dictionary looks like right now.

self.control_dict={"light.den_fan_light":{"light.den_fan_light":{"on":"50","off":"0","last_brightness":""}},
                   "light.den_fan":{"light.den_fan":{"on":"50","off":"0","last_brightness":""}},
                   "switch.breakfast_room_light":{"switch.breakfast_room_light":{"on":"on","off":"off"}},
                   "light.office_lights":{"light.office_lights":{"on":"50","off":"0","last_brightness":""}},
                   "light.office_fan":{"light.office_fan":{"on":"128","off":"0","last_brightness":""}},
                   "sensor.ge_32563_hinge_pin_smart_door_sensor_access_control_4_9":{"light.office_lights":{"on":"50","off":"0","last_brightness":""}},
                   "media_player.dentv":{"light.den_fan_light":{"on":"10","off":"last","last_brightness":""},
                                         "switch.breakfast_room_light":{"on":"off","off":"on"}},
                   "media_player.office_directv":{"light.office_lights":{"on":"10","off":"last","last_brigthness":""}},

The structure is “trigger”:{“target”:{“trigger_state”:“target_action”,“trigger_state”:“target_action”}}

I have around 45 or so trigger/target relationships right now. Right now it’s pretty simple, my trigger states are on and off, and my target actions are on/off/dim (if it’s a light). But I can see wanting my trigger state to be something derived by a function something like:
“sensor.upstairs_temp”:{“upstairs_fan”:{“sensor.upstairs_temp > sensor.downstairs_temp”:“on”,“else”:“off”}}

I’m just coming up with that off the top of my head, so the conditions are half baked. Hopefully you get the idea though.

I’ll probably have to revise my dictionary structure to do this, but the ability to evaluate a condition, or maybe call an external AD app or function might be useful.

Maybe I would use rene’s speach app as an action and want to call it as an action.

Does that make any sense??

I believe I understand what you’re trying to achieve … few things to recommend.

  • Even if a target does not have a last_value, I would include a space for one.
  • Trigger state being generated dynamically is pretty simple… you handle this logic based on your dictionary still, but define a function to do it
self.trigger_map = {
    "trigger_entity_id1": {
        "target_entity_id1": {
            "on": "True",
            "off": "False",
            "last_value": None
        },
        "target_entity_id2": {
            "on": "on",
            "off": "off",
            "last_value": "on"
        }
    },
    "trigger_entity_id2": {
        "target_entity_id3": {
            "on": "on",
            "off": "off",
            "last_value": "off"
        }
    },
    "trigger_entity_id3": {
        "target_entity_id4": {
            "on": self.determine_on_state,
            "off": self.determine_off_state,
            "last_value": None
        }
    }
}

The logic in your event (assume you’re listening to entities associated with the triggers-entities?) you’ll have a simple if/else statement that runs a function or process the string value.

if callable(self.trigger_map[entity]['on']):
    self.trigger_map[entity]['on']() # < ---- the function should take in arguments, or a flag, or something
else:
    # do your normal stuff here, as the value would be a string

Thanks,
I’ll give that a try. I get what you are saying about the last_values. The last value is the previous brightness of a light so that it can be returned to it’s previous brightness in the event the trigger_action is “last”. It wouldn’t make sense for a switch. But again I understand what you are saying.

would this work also?

self.trigger_map = {
    "trigger_entity_id3": {
        "target_entity_id4": {
            "on": self.bigger_then(value),
            "off": self.determine_off_state,
            "last_value": None
        }
    }
}
if callable(self.trigger_map[entity]['on']):
    self.trigger_map[entity]['on'] # < ---- the function should take in arguments, or a flag, or something
else:
    # do your normal stuff here, as the value would be a string

It’s the callable part that I was missing. How It’s stored in the dictionary may change over time as I add new features.

Sure! … except when you instantiate your trigger_map, value is unlikely to be defined yet. :wink: It is intended to be in the def initialize section of your app.

The if callable() statement has the same semantic meaning as "does this object have a __call__ function." which all functions have by default. It’s our little cheat that asks “am I looking at a function?” :slight_smile:

i named it value but it could be another function or a value then :wink:

for instance

self.trigger_map = {
    "trigger_entity_id3": {
        "target_entity_id4": {
            "on": self.bigger_then(self.get_state('sensor.temperature')),
            "off": self.determine_off_state,
            "last_value": None
        }
    }
}

Maybe I’m mis-understanding here.

I have the following record in my dictionary.

"device_tracker.scox1209_scox1209":{"input_boolean.masterishome": self.update_location("group.app_light_control_master","input_boolean.masterishome")},

I have a state_change watch that triggers a handler when the device_tracker changes state.

In the handler, if the result of self.control_dict[trigger][target] is callable, I want to call it.
trigger=“device_tracker.scox1209_scox1209” target = “input_boolean.masterishome”

When I look at the values just before check to see if it’s callable, self.control_dict[trigger][target]=None of course that isn’t callable. The function seems to be called when the dictionary in initialized and then it disappears.

That’s because python will write the dictionary and run the function immediately, since you’re asking to call it. My example is different from @ReneTode’s … which I do not think will work the way you’re expecting it.

Your record in the dictionary should look something like this …

some_dict = {
    ...
    "device_tracker.scox1209_scox1209": {
        "input_boolean.masterishome": self.update_location,
        "func_args": ["group.app_light_control_master", "input_boolean.masterishome"]
    },
    ...
}

and when you want to compute your value for the target, you would do.

*Note, this is where I believe you’re getting at with this… I’m still not 100% on what you’re trying to do to with group.app_light_control_master and input_boolean.masterishome.

# def proper_event_callback_function()
     entity = 'device_tracker.scox1209_scox1209'
     kwargs['target_entity'] = 'input_boolean.masterishome'
     
     # calculate values variably
     args = some_dict[entity][target_entity]['func_args']
     results = some_dict[entity][target_entity](args)
     
     # do interesting things

def update_location(self, *args):
    for arg in args:
        # do interesting things based on arguments

    return # interesting results in a data container (list, dict, tuple)

If you do it Rene’s way, you’re calculating those values at the time the dictionary is instantiated. initialize only runs 1 time, thus the value will only be derived 1 time. If you have the event be the trigger, then you’ll drive the value every time the event gets called which is what I believe you are expecting.

Cheers,

  • SN

Thanks, I see the difference now.

Here is the actual example of what I’m running into now. Each person in my house has a device_tracker (owntracks on their phone). Each person has their own room except my wife and I who share a room.
My automations turn off lights and adjust fans in my kids rooms based on when their device_trackers say they are home or away. it does that great. When my middle son leaves for the day, it cuts his ceiling fan down to half power, and turns off the lights in his room. Same for my youngest except he doesn’t care about the ceiling fan so it turns his all the way off. Each device_tracker/room combination is configurable separately. The problem comes to my wife and I. Like today, I am home, she is at work. I don’t want the lights in our room turning off on me just because she left for work.

I was using a group for this. I had both my wife and I’s trackers in a group. If the group was on, someone was home. When both of us left home, the group would turn it’s status off. Normal HA behavior. Unfortunately it’s not consistent behavior. With 0.43 it seems to be broken completely, the group no longer changes status no matter what status device trackers are in.

So I created an input_boolean (masterishome) and want to control it’s value based on the triggering of the device_trackers for my wife and I. On is not problem. Whenever a device tracker says we are home, just turn on masterishome and the automations catch that change and act appropriately. Off is the problem. Just because one of the trackers changes to not_home doesn’t necessairly mean that masterishome should turnoff. So I want to call the function at that point to determine the appropriate state and either turn on or off masterishome.

Are there other ways to accomplish this, yes. And to be honest right now, I have implemented one of those other ways just to keep things working, but it involves hard coding group names and I hate to hard code things. So I would like to use callable functions to solve the problem.

See why I was being a little vague in describing it. It’s a mouthful. :slight_smile:

Thanks

i would probably solve that with an easy not hardcoded way.

listen_state(self.someonehone,self.args["device1])
listen_state(self.someonehone,self.args["device2])

def someonehome(...):
  someone_home = False
  if self.get_state(self.args["device1"]) == "home":
    someone_home = True
  if self.get_state(self.args["device2"]) == "home":
    someone_home = True
  if someone_home:
    self.turn_on(self.args["group"])
  else:
    self.turn_off(self.args["group"])

off course you could create a list out of a HA group and test all the devices in the group.
if you do that in the listen state as well as in the get state, you would have a consistent group boolean.