Get sibling entities in Python script

Hey all! I hope I’m not trying to solve the wrong problem here. Here goes :slight_smile:

I am writing a python script where I am making decisions about what to do with a device’s entity based on one of its other entity’s states.

I’m basically making a power shedding setup. If my total current is above X, then I want to check each of my relays’ current at the moment and turn one or more of them off until the current is below a threshold. Each relay is a separate esphome device with a current sensor, a relay, and some other entities.

I think what I want to do is this:

  • Given {current_sensor_id}, get the state. If total current > threshold, get the device for {current_sensor_id}.
  • Look at all entities for the device, and find the relay switch. Turn it off.

I see that helpers can do some of this, but it appears they are not accessible from within python scripts.

If there is a better way to do this (preferably without doing string substitution on Ids,) I’m all ears. This is just how I decided to approach it :slight_smile:

Thanks in advance!

That sounds like you could do it in a normal automation or script. Can you post an example device with its set of entities?

How do you decide in what order to check your devices?

Choosing the priority order of entities is a TODO in the code currently :slight_smile:

As a first cut, I want to get the logic working, but eventually I might add an entity to each device to hold the priority or something.

Here is the ESPHome yaml for an instance:

substitutions:
  location: garage
  locationName: Boys' Room
  device: sonoff_minifridge
  deviceName: Mini Fridge
  deviceDescription: Sonoff S31 to control mini fridge
  ipAddress: 192.168.50.107

# import common components
packages:
  common: !include sonoff_switch_template.yaml

And sonoff_switch_template.yaml:

# Basic Config
esphome:
  platform: ESP8266
  board: esp01_1m

# import common components
packages:
  common: !include common/common.yaml

wifi:
  use_address: "$ipAddress"
  manual_ip:
    static_ip: $ipAddress


uart:
  rx_pin: RX
  baud_rate: 4800

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: True
    name: "$deviceName Button"
    on_press:
      - switch.toggle: relay
  - platform: status
    name: "$deviceName Status"

  - platform: template  # this is to be able to tell if the power shedding logic turned off the switch, and therefore it should be turned back on if/when available current capacity exists again
    name: "$deviceName disabled by shedding"


sensor:
  - platform: wifi_signal
    name: "$deviceName WiFi Signal"
    update_interval: 60s
  - platform: cse7766
    current:
      name: "$deviceName Current"
      accuracy_decimals: 2
      filters:
        - throttle_average: 60s
    voltage:
      name: "$deviceName Voltage"
      accuracy_decimals: 2
      filters:
        - throttle_average: 60s
    power:
      name: "$deviceName Power"
      accuracy_decimals: 2
      id: my_power
      filters:
        - throttle_average: 60s
    energy:
      name: "$deviceName Energy"
      accuracy_decimals: 2
      filters:
        - throttle: 60s
    apparent_power:
      name: "$deviceName Apparent Power"
      filters:
        - throttle_average: 60s
    power_factor:
      name: "$deviceName Power Factor"
      accuracy_decimals: 2
      filters:
        - throttle_average: 60s

  - platform: total_daily_energy #(Optional, not specific to cse7766)
    name: "$deviceName Daily Energy"
    power_id: my_power
    accuracy_decimals: 2

switch:
  - platform: gpio
    name: "$deviceName Relay"
    pin: GPIO12
    id: relay
    restore_mode: RESTORE_DEFAULT_ON

time:
  - platform: sntp #(required for total_daily_energy)
    id: my_time

status_led:
  pin:
    number: GPIO13
    inverted: True

FYI common.yaml includes things like api config, the rest of wifi, ota, etc.

If what I am doing can be done in a normal automation or script, is there a reason I wouldn’t be able to do so in Python?

Update (ish) but not using python (rather using a jinja template):

I found a project in Italian that does exactly what I wanted to do, so I ported it to English: GitHub - mavenius/Power-Control-HomeAssistant: Progetto per gestire in modo automatico gli assorbimenti degli elettrodomestici per evitare distacchi di corrente

# priority-ordered list of entities to control (first is lowest priority and will be turned off first)
{% set list_entities =
    [
       'switch.yeti_battery_charger_relay',
       'switch.converter_relay',
       'switch.water_heater_relay',
       'switch.fireplace_relay',
       'climate.boys_bedroom_heater_thermostat',
       'climate.master_bedroom_heater_thermostat',
       'switch.dryer_relay',
       'switch.clam_lights_relay',
       'switch.side_deck_relay',
       'switch.mini_fridge_relay',
       'switch.dishwasher_relay',
       'switch.microwave_relay',
       'switch.washer_relay'
    ]
%}

##############################################################
#         FROM THIS POINT DO NOT MODIFY
##############################################################
{% macro dict_device() %}
    {% set output = namespace(sensor_power_off=[], general_entity_on=[]) %}
    {
        {%- for entity_on in list_entities if has_value(entity_on) -%}
            {%- set output.general_entity_on = output.general_entity_on + [entity_on] -%}
            {# retrieve all device entities #}
            {%- set entities_devices = entity_on | device_id | device_entities -%}
                {# Return entity_id of power sensor if there is one associated to the given device #}
                {% set list_sensor_power = expand(entities_devices) | selectattr('state', 'is_number') | selectattr('domain', 'eq', 'sensor') | selectattr('attributes.device_class', 'defined') | selectattr('attributes.device_class', 'eq', 'power') | map(attribute='entity_id') | list %}
                {% set sensor_power = list_sensor_power[0] if list_sensor_power | count == 1 else 'unknown' %}
                "{{ state_attr(entity_on, 'friendly_name') }}": 
                {
                "entity" : "{{ entity_on }}",
                "state" : "{{ states(entity_on) }}",
                "sensor_power" : "{{ sensor_power }}",
                "power" : "{{ states(sensor_power) if sensor_power != 'unknown' else 'unknown'  }}",
                "real_active" : "{{ 'on' if (not is_state(entity_on, 'off') and sensor_power == 'unknown') or sensor_power != 'unknown' and states(sensor_power) | int(0) > 15 else 'off' }}"
                }
                {%- if not loop.last %},{% endif %}
        {% endfor -%}
    }
{% endmacro %}

What is happening here is that list_entities is the priority-ordered list of entities that we care about (mostly switches, but also climate objects that can be turned on or off.) In the dict_device macro, it grabs the device Ids for each of the entities in the list, then gets the devices for those Ids. It then gets a power sensor associated with the corresponding device and pulls the current consumption from that.

Downstream, a few automations trigger off of this data plus some limits and present high-level usage so switches can be turned off in order to get below the limit(s) but that is beyond the scope of this thread. Feel free to see the linked github project for those details if you’re interested.