Home assistant power saver

home assistantpower savernordpool

March 17, 2023

I've seen a couple of different variants of Power saving scripts for Home Assistant. This is my take on it built using a template entity, a couple of input_number fields and the Nordpool integration.
My first step is to setup 3 input_number helper entities.

  • power_min_hours_on (0-24, number of hours minimum required on)
  • power_max_price (if price is above this the power will always be off, regardless of power_min_hours)
  • power_percent_over_min (How many percent of the daily minimum price to count as cheap and allow power to be on)

Then I implement the following binary template sensor. Be sure to change the nordpool sensor id to match yours!

Edit: Fixed issue with daylight savings time.

- binary_sensor:
    - name: Power saver
      unique_id: powersaver
      device_class: problem
      state: '{{ not (this.attributes.schedule | selectattr("current") | first | default({"onOff":false})).onOff }}'
      attributes:
        schedule: >
          {% set data = namespace(sorted=[],activated=10) %}
          {% set data.activated = states("input_number.power_min_hours_on") | int(10) %}
          {% set minperc = (states("input_number.power_percent_over_min") | int(15) + 100.0) / 100.0 %}
          {% set maxprice = states("input_number.power_max_price") | float(0) %}

          {% set sorted = state_attr("sensor.nordpool_kwh_se4_sek_3_10_025", "raw_today") | selectattr("value") | sort(attribute="value") %}
          {% set breakprice = minperc * sorted[0].value %}
          {% for s in sorted %}
            {% set onOff = (s.value <= breakprice or data.activated > 0) and s.value < maxprice %}
            {% set current = s.start < now() and s.end > now() %}
            {% set data.sorted = data.sorted + [{"price": s.value, "time": as_timestamp(s.start) | timestamp_local, "onOff": onOff, "current": current }] %}
            {% set data.activated = data.activated - (1 if onOff else 0) %}
          {% endfor %}

          {% set sorted = state_attr("sensor.nordpool_kwh_se4_sek_3_10_025", "raw_tomorrow") | selectattr("value") | sort(attribute="value") %}
          {% if sorted | length > 0 %}
            {% set data.activated = states("input_number.power_min_hours_on") | int(10) %}
            {% set breakprice = minperc * sorted[0].value %}
            {% for s in sorted %}
              {% set onOff = (s.value <= breakprice or data.activated > 0) and s.value < maxprice %}
              {% set data.sorted = data.sorted + [{"price": s.value, "time": as_timestamp(s.start) | timestamp_local, "onOff": onOff, "current": false }] %}
              {% set data.activated = data.activated - (1 if onOff else 0) %}
            {% endfor %}
          {% endif %}
          {{ data.sorted | sort(attribute="time") | list }}

As a bonus I've created a nice little chart using the apexcharts-card.

apexchart-card

type: custom:apexcharts-card
update_interval: 11m
graph_span: 1d
header:
  show: true
  title: Today
apex_config:
  chart:
    type: area
  legend:
    show: false
  fill:
    type: gradient
    gradient:
      shadeIntensity: 0.1
      opacityFrom: 0.55
      opacityTo: 1
      inverseColors: true
      stops:
        - 0
        - 90
        - 100
span:
  start: day
  offset: "-0d"
now:
  show: true
  color: rgb(138,43,226)
  label: Now
yaxis:
  - id: price
    decimals: 2
    min: "|-0.1|"
    max: "|+0.1|"
    apex_config:
      title:
        text: kr/kWh
      forceNiceScale: true
  - id: powersaver
    show: false
    min: 0
    max: 1
series:
  - entity: sensor.nordpool_kwh_se4_sek_3_10_025
    yaxis_id: price
    type: line
    color: rgb(128,128,128)
    data_generator: |
      return entity.attributes.raw_today.map((entry) => {
        const then = new Date(entry.start.split('+')[0]);
        return [then, parseFloat(entry.value)];
      });
    name: Elpris
    curve: stepline
    stroke_width: 3
  - entity: binary_sensor.powersaver
    data_generator: |
      return entity.attributes.schedule.map((entry) => {
        return [new Date(entry.time), entry.onOff ? 1 : 0];
      });
    yaxis_id: powersaver
    name: " "
    type: area
    color: rgba(0, 255, 0, 0.2)
    opacity: 0.1
    stroke_width: 0
    curve: stepline
    group_by:
      func: min
    show:
      legend_value: false
      in_header: false
      name_in_header: false
      datalabels: false