Strange response reading in YAML dictionaries between edits

I 've been using AD for a while now, and use extensive use of YAML files for the inputs. I just ran into an issue i can’t explain. To check it out, i created the following test code and YAML file:
Test code followed by the YAML file:

import appdaemon.plugins.hass.hassapi as hass
import datetime
import inspect

class TestYamlRead(hass.Hass):

      def initialize(self):
        # Only executes the yaml read function
        self.read_data_from_yaml()

      def read_data_from_yaml(self):
        self.motion_dict = {}
        self.motion_dict  = self.args["motion_actions"] #Read data from "motion_actions" section
        self.log("set motion_dict: {}".format(self.motion_dict)) # Print to the log
        for motion in self.motion_dict:
            if "countdown_sensor" in self.motion_dict[motion]:
                self.motion_dict[motion]["countdown_sensor"] = "sensor."+self.motion_dict[motion]["countdown_sensor"] # Update "countdown_sensor" entry with the word "sensor." I use the same name to create a binary_sensor., which is why i do it this way
motion_sensor_actions_test:
  module: test_yaml_read
  class: TestYamlRead
  dependencies: []
  motion_actions_test:
motion_training_room_ksc_long:
  motion_sensor: binary_sensor.training_room_motion_sensor
  on_action: toggle_scene
  countdown_sensor: timer_training_room
  delay: 120

Here’s the strange part. Whenever i edit the py file, an additional “sensor.” gets added to the beginning of countdown_sensor. The log output from simply adding a space and resaving the py file is:
motion_sensor_actions_test: set motion_dict: {‘motion_training_room_ksc_long’: {‘motion_sensor’: ‘binary_sensor.training_room_motion_sensor’, ‘on_action’: ‘toggle_scene’, ‘countdown_sensor’: ‘timer_training_room’, ‘delay’: 120}}
#2:
motion_sensor_actions_test: set motion_dict: {‘motion_training_room_ksc_long’: {‘motion_sensor’: ‘binary_sensor.training_room_motion_sensor’, ‘on_action’: ‘toggle_scene’, ‘countdown_sensor’: ‘sensor.timer_training_room’, ‘delay’: 120}}
#3:
motion_sensor_actions_test: set motion_dict: {‘motion_training_room_ksc_long’: {‘motion_sensor’: ‘binary_sensor.training_room_motion_sensor’, ‘on_action’: ‘toggle_scene’, ‘countdown_sensor’: ‘sensor.sensor.timer_training_room’, ‘delay’: 120}}

So it seems that the self.args[] is staying in-sync with the motion_dict, even though it’s re-read each time. I added the line self.args.clear to try and clear it out, but that didn’t do anything (just added another sensor. to the beginning). The same behavior occurs if I add anything to the motion_dict - it is then mirrored in self.args.

Anyone know what is going on? I can work around it, but I like this method a little better.

Just solved my own issue - whenever one dictionary is set equal to another dictionary, a copy isn’t really made - the references to the values are copied into the second dictionary, which is why changing one also changes the other. Unless you do a complete restart of AD, the dictionaries apparently persist across reloads.

The way around this is to change the following line to the one just below it:

   self.motion_dict  = self.args["motion_actions_test"]
   self.motion_dict  = copy.deepcopy(self.args["motion_actions_test"])

To use deepcopy, you also have to import copy at the top of the module.

I tested this and it works… Hope this saves someone else some time.

Glad that you solved your problem :slight_smile:

Your post made me curious. I tried to understand, but I didn’t get it.
Could you maybe explain a bit what the purpose of this YAMLread class is?

Sure! The entire idea around using Appdaemon is that you can write a more generic python class that can then be re-used. So for example, I have a generic RoomActions python class that handles the controllable media, lights, and switches in the room. I then use different YAML files to “describe” the components in the room, and what i want to do with them, as well as a Hue 4-button Dimmer switch (if it exists in the room).

For example, following is a YAML input for the training room Hue Switch:

hue_switch_training_room:
  module: hue_switch_template
  class: HueSwitch
  dependencies:
    - utilities
    - entity_dbase
  trigger_hue_switch: training_room_switch
  button_1_up: toggle_scene_sjc
  button_1_hold: toggle_scene_ksc
  button_2_up: brighten_tr_light
  button_2_hold: brighten_basement_bath_lights
  button_3_up: dim_tr_light
  button_3_hold: dim_basement_bath_lights
  button_4_up: shut_down_training_room

The python class HueSwitch simply toggles input_booleans that are named in the button line (i.e., input_boolean.toggle_scene_ksc.

Then, in the same YAML file, I include the following:

 training_room_actions_r1:
      module: room_actions_template_r1
      class: RoomActions
      dependencies:
        - utilities
        - entity_dbase
        - util_media
        - util_lights_switches
      input_booleans:
        - toggle_scene_sjc
        - toggle_scene_ksc
        - shut_down_training_room
        - dim_tr_light
        - dim_basement_bath_lights
        - brighten_tr_light
        - brighten_basement_bath_lights
        - turn_on_tr_tv

These are all booleans that are read in by RoomActions using the read_yaml_file method:

self.input_booleans = self.args["input_booleans"]

The RoomActions initialize section sets callbacks for each of the above booleans:

   for ib in self.input_booleans:                                          
     self.listen_state_handle_list.append(self.listen_state(self.cb_action_state, "input_boolean."+ib, new="on"))

I then have sections that describes what to do whenever one of these booleans is triggered (turn on a light, turn off a light, etc). For example:

  toggle_scene_sjc:
    light1:
      action: turn_on
      fname: "Training Room"
      entity: light.training_room
      bright: 250
    light2:
      action: turn_off
      entity: light.basement_bathroom
      fname: 'Basement Bathroom'
    roku:
      action: turn_on
      fname: 'TR Roku'
      channel: YouTube
    switch1:
      action: turn_on
      fname: 'Smart Plug TR Fans'

Using this technique, i have a YAML file for each room that starts another instance of RoomActions. The entire premise is that something triggers an input_boolean that is described in one of these files. Then the system takes actions described for that input_boolean.

Could i have done this in the basic HASS script and automation? That’s where i started. That became very complex very quickly. And scripts are not readily reusable (I’m big on reusable code - i only want to write it once, so if I ever decide to modify the logic, then I only have to do it in one spot).

Hope this gives you few ideas to get started with!

Thanks for the explanation. I’m using Appdaemon myself for quite some time now, I was just wondering about your approach.

To be honest I still don’t get why you are doing it this way.
If I understood correctly, you have an input_boolean for each room for each possible action of the dimmer switch and when a button is pressed it toggles the corresponding input_boolean and you listen for state changes of each of theses input_booleans and then trigger actions based on changes in these.

Why don’t you trigger the actions directly when the button is pressed? This way you only need to listen for the state change of the dimmer switch and then trigger the corresponding action. Your approach registers a lot of unnecessary callbacks or am I missing something?

For example I have a “hue_dimmer_switch” class which listens for an event of the hue dimmer switch and it then triggers the action I configured in the YAML file which corresponds to the button pressed.

Thanks for the suggestion. I’m pretty sure i’m not optimal, and am always looking for ways to streamline the code.

Are you using Rob Cole’s hue dimmer switch component? And can you show some of your code? I have a number of hue dimmer switches, which i is why i need (i think) put the actions in a yaml file so i can reuse the code - i didn’t want to hard code actions specific to a hue dimmer button (click or long press) in the python class.

Thoughts?

I don’t know about Rob Cole’s component. I have a Conbee Stick and am using the deconz component. I get an event whenever a button is pressed on the hue dimmer switch. How do you recognize a button press on the hue dimmer switch?

I’m using it a bit differently then you are. I use the scenes in home assistant itself for multiple actions at once because it is faster than doing it in appdaemon. Then I just create a scene and assign that scene to a button press of the hue dimmer switch.

Regarding code, I hacked together some lines that you could try. If I get the time I will test the code myself.

class DimmerSwitch(hass.Hass):
    def initialize(self) -> None:
        self.button_config = self.args['button_config']

        if 'dimmer_switch' in self.args:
            # take action when button is pressed on dimmer switch
            self.listen_event(self.button_pressed,
                              "deconz_event",
                              id=self.args['dimmer_switch'])

    def button_pressed(self, event_name: str, data: dict,
                       kwargs: dict) -> None:
        button_event = data['event']
        # 10xx: on, 20xx: brighten, 30xx: dim, 40xx: off
        # xx03: long press release, xx02: short press release

        if button_event in self.button_config:
            service_conf = self.button_config[button_event]
            service = service_conf['entity'].split('.')[0]

            if service == 'scene':
                self.turn_on(service_conf['entity'])
            else:
                self.call_service(
                    f"{service}/{service_conf['action']}",
                    entity_id=service_conf['entity'],
                    **service_conf['parameters']
                )
        else:
            self.log("Button not configured")

An example YAML file:

dimmer_switch_trainingroom:
  module: dimmer_switch
  class: DimmerSwitch
  dimmer_switch: training_room_switch
  button_config:
    1002:
      entity: media_player.roku
      action: turn_on
      parameters:
        channel: YouTube
    2002:
      entity: scene.training

Like this on a button press 1002 (short press ‘on’ button) the roku will be turned on to the channel Youtube. On a button press 2002 (short press ‘brightness up’ button) the scene ‘training’ will be turned on. This scene would then be a scene you configure in home assistant that turns on light 1, turns on roku and so on.

You can adjust this to your liking, turn on a light, call a service with parameters or start a scene. If you try the code I would be glad to get some feedback, I can also help you with any errors with my code, which I’m sure will appear :slight_smile: