Open-Meteo Solar Forecast

I have this defined in a sensors.yaml file and in configuration.yaml a line

sensor: !include sensors.yaml

and then the sensor shows under “Helpers” in the UI?

I think (s)he means the data type is different between yours and the original sensor (indicated by the dashes)

Yes, but that does not matter. You can even make it output similar to the solcast sensor by modifying {% set output_item = {time:sum_value} %} to {% set output_item = {'period_start': time, 'pv_wh_estimate':sum_value} %} i.e.

- period_start: '2024-06-10T07:00:00+02:00'
  pv_wh_estimate: 770.25
- period_start: '2024-06-10T08:00:00+02:00'
  pv_wh_estimate: 1860.5
- period_start: '2024-06-10T09:00:00+02:00'
  pv_wh_estimate: 3024.25
- period_start: '2024-06-10T10:00:00+02:00'
  pv_wh_estimate: 4023.75

Your one is a list and the other is dictanory.
I have no idea with this grab of jinja2 dsl how to cast or create a dic instead of a list

I don’t have multiple PV arrays so I can’t test but does something like this work? I’m a beginner at Jinja/templating (obviously adapt for wh_period, etc; this is an example for watts):

{% set sensor1 = state_attr('sensor.energy_production_today_2', 'watts') %}
{% set sensor2 = state_attr('sensor.energy_production_today_3', 'watts') %}
{% set ns = namespace(output={}) %}

{% for time, value in sensor1.items() %}
    {% set ns.output = dict({time: value}, **ns.output) %}
{% endfor %}

{% for time, value in sensor2.items() %}
    {% if time in ns.output %}
        {% set ns.output = dict({time: ns.output[time] + value}, **ns.output) %}
    {% else %}
        {% set ns.output = dict({time: value}, **ns.output) %}
    {% endif %}
{% endfor %}

{{ ns.output }}

Edit: Jinja is really ugly and I hate it.

2 Likes

Yes that works

- platform: template
  sensors:
    energy_production_today_sum:
      friendly_name: "Energy Production Today Sum"
      unit_of_measurement: "kWh"
      device_class: energy
      value_template: >-
        {% set sensor1 = states('sensor.energy_production_today_2') |float %}
        {% set sensor2 = states('sensor.energy_production_today_3') |float %}
        {% set total_watts = sensor1 + sensor2 %}
        {{ total_watts }}
      attribute_templates:
        watts: >-
          {% set sensor1 = state_attr('sensor.energy_production_today_2', 'watts') %}
          {% set sensor2 = state_attr('sensor.energy_production_today_3', 'watts') %}
          {% set ns = namespace(output={}) %}

          {% for time, value in sensor1.items() %}
              {% set ns.output = dict({time: value}, **ns.output) %}
          {% endfor %}

          {% for time, value in sensor2.items() %}
              {% if time in ns.output %}
                  {% set ns.output = dict({time: ns.output[time] + value}, **ns.output) %}
              {% else %}
                  {% set ns.output = dict({time: value}, **ns.output) %}
              {% endif %}
          {% endfor %}

          {{ ns.output }}
        wh_period: >-
          {% set sensor1 = state_attr('sensor.energy_production_today_2', 'wh_period') %}
          {% set sensor2 = state_attr('sensor.energy_production_today_3', 'wh_period') %}
          {% set ns = namespace(output={}) %}

          {% for time, value in sensor1.items() %}
              {% set ns.output = dict({time: value}, **ns.output) %}
          {% endfor %}

          {% for time, value in sensor2.items() %}
              {% if time in ns.output %}
                  {% set ns.output = dict({time: ns.output[time] + value}, **ns.output) %}
              {% else %}
                  {% set ns.output = dict({time: value}, **ns.output) %}
              {% endif %}
          {% endfor %}

          {{ ns.output }}
1 Like

This is a poll for how to handle the multiple PV array case:

  • Support multiple PV arrays in this integration via configuration.yaml
  • Have people resort to template sensors instead
0 voters

Context, the support is already implemented in Implement support for multiple PV arrays by rany2 · Pull Request #8 · rany2/open-meteo-solar-forecast · GitHub but needs changes on the HA side.

@rany and @slipx06
Did not work for me. This looks to work for me:

sensor:
  - name: "energy_production_today_all"
    state: >
      {{ (states('sensor.energy_production_today') | float(default=0) + states('sensor.energy_production_today_2') | float(default=0)) | round(2) }}
    unit_of_measurement: "kWh"
    device_class: energy
    attributes:
      watts: >-
        {% set sensor1 = state_attr('sensor.energy_production_today', 'watts') %}
        {% set sensor2 = state_attr('sensor.energy_production_today_2', 'watts') %}
        {% set ns = namespace(output={}) %}
        {% for time, value in sensor1.items() %}
          {% set sum_value = value + sensor2[time] %}
          {% set ns.output = dict({time: sum_value}, **ns.output) %}
        {% endfor %}
        {{ ns.output }}
      wh_period: >-
        {% set sensor1 = state_attr('sensor.energy_production_today', 'wh_period') %}
        {% set sensor2 = state_attr('sensor.energy_production_today_2', 'wh_period') %}
        {% set ns = namespace(output={}) %}
        {% for time, value in sensor1.items() %}
          {% set sum_value = value + sensor2[time] %}
          {% set ns.output = dict({time: sum_value}, **ns.output) %}
        {% endfor %}
        {{ ns.output }}

Thanks for the hints

For the new peaktime I did also something ugly but work so far:

  - name: "power_highest_peak_time_today_all"
    state: >
      {%set watts=states.sensor.energy_production_today_all.attributes['watts']%}
      {%set highestWatt=namespace(value=0) %}
      {%set endDate=namespace(value=0) %}
      {%for date in watts %}
      {%set watt = watts[date]%}
        {%if watt>highestWatt.value%}
        {%set highestWatt.value = watt%}
        {%set endDate.value = date%}
        {% endif %}
      {% endfor %}
      {{endDate.value}}
    device_class: timestamp

Bildschirmfoto 2024-06-11 um 09.18.55

@rany so iam happy with configuration.yaml it works :slight_smile:

Maybe anybody have an idea why the overall kwh in the apex chart is not working

1 Like

Just keep in mind that sensor2[time] might not exist in some rare cases so you should make it default to 0 somehow.

1 Like
This error originated from a custom integration.

Logger: custom_components.open_meteo_solar_forecast
Source: helpers/update_coordinator.py:312
integration: Open-Meteo Solar Forecast (documentation, issues)
First occurred: June 10, 2024 at 20:01:00 (1138 occurrences)
Last logged: 02:00:00

Unexpected error fetching open_meteo_solar_forecast data
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 312, in _async_refresh
    self.data = await self._async_update_data()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/open_meteo_solar_forecast/coordinator.py", line 63, in _async_update_data
    return await self.forecast.estimate()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/open_meteo_solar_forecast/open_meteo_solar_forecast.py", line 155, in estimate
    data = await self._request(
           ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/open_meteo_solar_forecast/open_meteo_solar_forecast.py", line 122, in _request
    raise OpenMeteoSolarForecastRatelimitError("Rate limit exceeded")
open_meteo_solar_forecast.exceptions.OpenMeteoSolarForecastRatelimitError: Rate limit exceeded

1138 occurrences, it means there are 1138 api calls in 6 hours?

No it just retries until the rate limit is lifted, after it succeeds it polls the API every 30mins. I could make my personal Open-Meteo instance available to others for use if needed.

It’s caused by your IP being shared with others, Open-Meteo is popular outside of HA (apps like Breezy Weather use it) so you probably have a neighbor that’s into FOSS services.

ok good to know then i have to look how make a default value or is this possible to handle in the library?

What does it mean that my IP is being shared with others? Could it be because I am using cloudflared for remote access to home assistant? I would be glad if you could help me find a solution for this issue.

It’s really unrelated to this integration but I’ll explain in simple terms. Basically because there are only ~4B public IPv4 addresses available (and >8B people, servers, devices, etc) ISPs are forced to basically use something like NAT44 so that all their users could access the IPv4 internet. One of the many issues with technologies like NAT44 is that it means that a large chunk of the ISP’s users (sometimes even an entire city) would share the same IPv4 address. That is why your Open-Meteo quota is exhausted so quickly, there are other people also using Open-Meteo with the same public IPv4 address.

I’ve opened a discussion on GitHub to ask if API keys could be made available for free users, this way the quota is per-API key not IP address: API keys for the free Open-Meteo API · open-meteo/open-meteo · Discussion #843 · GitHub. If it doesn’t work (I’m 99% sure it’ll be rejected) I could just make my own personal instance available, I feel like I would probably be able to cope with the load.

3 Likes

Thank you for your explanation. I thought there could only be one public ip-address at the same time. I think there is no way for me to solve this problem, is it? It would be great if you could offer a solution. I tink 10 daily calls per instance (I have got four) should be enough at it was with solcast either.

I tried to use 4 Sensors to sum up but I get the following errors, perhaps you can help me out:

TemplateError('TypeError: unsupported operand type(s) for +: 'int' and 'dict'') while processing template 'Template<template=({% set sensor1 = state_attr('sensor.energy_production_today_8', 'watts') %} {% set sensor2 = state_attr('sensor.energy_production_today_9', 'watts') %} {% set sensor3 = state_attr('sensor.energy_production_today_10', 'watts') %} {% set sensor4 = state_attr('sensor.energy_production_today_11', 'watts') %} {% set ns = namespace(output={}) %} {% for time, value in sensor1.items() %} {% set sum_value = value + sensor2 + sensor3 + sensor4[time] %} {% set ns.output = dict({time: sum_value}, **ns.output) %} {% endfor %} {{ ns.output }}) renders=4>' for attribute 'watts' in entity 'sensor.energy_production_today_all'
TemplateError('TypeError: unsupported operand type(s) for +: 'float' and 'dict'') while processing template 'Template<template=({% set sensor1 = state_attr('sensor.energy_production_today_8', 'wh_period') %} {% set sensor2 = state_attr('sensor.energy_production_today_9', 'wh_period') %} {% set sensor3 = state_attr('sensor.energy_production_today_10', 'wh_period') %} {% set sensor4 = state_attr('sensor.energy_production_today_11', 'wh_period') %} {% set ns = namespace(output={}) %} {% for time, value in sensor1.items() %} {% set sum_value = value + sensor2 + sensor3 + sensor4[time] %} {% set ns.output = dict({time: sum_value}, **ns.output) %} {% endfor %} {{ ns.output }}) renders=4>' for attribute 'wh_period' in entity 'sensor.energy_production_today_all'

Maybe if you send the yaml code:
I see sensor 3 in the error code, but I see in my post no sensor3 here:

So if you have 4 sensor you have todo more sums

Apex Charts are nice, plotly graphs are better :smile:

You’ll need to create some template sensors so its not a straight drop in.

yaml
type: custom:plotly-graph
hours_to_show: 4d
time_offset: 2.5d
refresh_interval: auto
entities:
  - entity: sensor.sunsynk_solar_power
    name: Solar Power
    line:
      color: rgb(255, 155, 48)
      shape: spline
      width: 1
    fill: tozeroy
    fillcolor: rgba(255, 155, 48, 0.3)
    yaxis: y3
    show_value: false
    showlegend: false
  - entity: sensor.energy_production_today_sum
    line:
      color: rgb(125, 125, 125)
      shape: spline
    fill: tozeroy
    filters:
      - fn: |
          ({meta}) => ({
             xs: meta.wh_period.map(({ start }) => new Date(start)),
             ys: meta.wh_period.map(({ value }) => value),
          })
    yaxis: y1
    showlegend: true
  - entity: sensor.energy_production_tomorrow_sum
    line:
      color: rgb(90,90,90)
      shape: spline
    fill: tozeroy
    filters:
      - fn: |
          ({meta}) => ({
             xs: meta.wh_period.map(({ start }) => new Date(start)),
             ys: meta.wh_period.map(({ value }) => value),
          })
    yaxis: y1
  - entity: sensor.energy_production_d2_sum
    line:
      color: rgb(50,50,50)
      shape: spline
    fill: tozeroy
    filters:
      - fn: |
          ({meta}) => ({
             xs: meta.wh_period.map(({ start }) => new Date(start)),
             ys: meta.wh_period.map(({ value }) => value),
          })
    yaxis: y1
    showlegend: true
  - entity: sensor.sunsynk_battery_soc
    name: Battery
    line:
      color: red
      shape: spline
      width: 1
    yaxis: y2
    show_value: true
    showlegend: false
    visible: true
  - entity: ''
    name: Now
    yaxis: y9
    showlegend: false
    line:
      width: 2
      dash: dot
      color: aqua
    x: $ex [Date.now(), Date.now()]
    'y':
      - 0
      - 1
layout:
  height: 400
  margin:
    t: 55
    l: 50
  showlegend: false
  legend:
    x: 0.1
    'y': -0.8
  yaxis2:
    range:
      - 20
      - 105
    fixedrange: true
  annotations:
    - text: |-
        $fn ({hass}) =>
          "<span style='font-size: 24px;'><span style='color: orange'>"
          + Number(hass.states['sensor.energy_production_tomorrow_sum'].state).toFixed(1) + "</span></span>kWh<br>"
          +  "</span><span style=''>Forecast Today</span><br>"
      xref: x domain
      yref: y domain
      xanchor: center
      yanchor: center
      x: 0.06
      'y': 1.2
      showarrow: false
    - text: |-
        $fn ({hass}) =>
          "<span style='font-size: 24px;'><span style='color: dodgerblue'>"
          + Number(hass.states['sensor.meteo_energy_production_today_remaining'].state).toFixed(1) + "</span></span>kWh<br>"
          +  "</span><span style=''>Remaining</span><br>"
      xref: x domain
      yref: y domain
      xanchor: center
      yanchor: center
      x: 0.28
      'y': 1.2
      showarrow: false
    - text: |-
        $fn ({hass}) =>
          "<span style='font-size: 24px;'><span style='color: '>"
          + Number(hass.states['sensor.energy_production_tomorrow_sum'].state).toFixed(1) + "</span></span>kWh<br>"
          +  "</span><span style=''>Forecast Tomorrow</span><br>"
      xref: x domain
      yref: y domain
      xanchor: center
      yanchor: center
      x: 0.5
      'y': 1.2
      showarrow: false
    - text: |-
        $fn ({hass}) =>
          "<span style='font-size: 24px;'><span style='color: white'>"
          + Number(hass.states['sensor.energy_production_d2_sum'].state).toFixed(1) + "</span></span>kWh<br>"
          +  "</span><span style=''>Forecast Day 2</span><br>"
      xref: x domain
      yref: y domain
      xanchor: center
      yanchor: center
      x: 0.72
      'y': 1.2
      showarrow: false
    - text: |-
        $fn ({hass}) =>
          "<span style='font-size: 24px;'><span style='color: red'>"
          + Number(hass.states['sensor.sunsynk_battery_soc'].state) + "</span></span>%<br>"
          +  "</span><span style=''>SOC</span><br>"
      xref: x domain
      yref: y domain
      xanchor: center
      yanchor: center
      x: 0.94
      'y': 1.2
      showarrow: false
config:
  scrollZoom: false


If you are using the sensors provided by the integration you can replace the filters that generates the data i.e:

Replace

    filters:
      - fn: |
          ({meta}) => ({
             xs: meta.wh_period.map(({ start }) => new Date(start)),
             ys: meta.wh_period.map(({ value }) => value),
          })

with

    filters:
      - fn: |
          ({meta}) => {
            const entries = Object.entries(meta.wh_period); 
            const xs = entries.map(([start, _]) => new Date(start));
            const ys = entries.map(([_, value]) => value);
            
            return { xs, ys };
          }
4 Likes

Here is my code for today and tomorrow for 4 sensors but something is wrong.

  - sensor:
      - name: "energy_production_today_all"
        state: >
          {{ (states('sensor.energy_production_today_8') | float(default=0) + states('sensor.energy_production_today_9') | float(default=0) + states('sensor.energy_production_today_10') | float(default=0) + states('sensor.energy_production_today_11') | float(default=0)) | round(2) }}
        unit_of_measurement: "kWh"
        device_class: energy
        attributes:
          watts: >-
           {% set sensor1 = state_attr('sensor.energy_production_today_8', 'watts') %}
           {% set sensor2 = state_attr('sensor.energy_production_today_9', 'watts') %}
           {% set sensor3 = state_attr('sensor.energy_production_today_10', 'watts') %}
           {% set sensor4 = state_attr('sensor.energy_production_today_11', 'watts') %}
            {% set ns = namespace(output={}) %}
            {% for time, value in sensor1.items() %}
              {% set sum_value = value + sensor2 + sensor3 + sensor4[time] %}
              {% set ns.output = dict({time: sum_value}, **ns.output) %}
            {% endfor %}
            {{ ns.output }}
          wh_period: >-
           {% set sensor1 = state_attr('sensor.energy_production_today_8', 'wh_period') %}
           {% set sensor2 = state_attr('sensor.energy_production_today_9', 'wh_period') %}
           {% set sensor3 = state_attr('sensor.energy_production_today_10', 'wh_period') %}
           {% set sensor4 = state_attr('sensor.energy_production_today_11', 'wh_period') %}
            {% set ns = namespace(output={}) %}
            {% for time, value in sensor1.items() %}
              {% set sum_value = value + sensor2 + sensor3 + sensor4[time] %}
              {% set ns.output = dict({time: sum_value}, **ns.output) %}
            {% endfor %}
            {{ ns.output }}
            
  - sensor:
      - name: "energy_production_tomorrow_all"
        state: >
          {{ (states('sensor.energy_production_tomorrow_8') | float(default=0) + states('sensor.energy_production_tomorrow_9') | float(default=0) + states('sensor.energy_production_tomorrow_10') | float(default=0) + states('sensor.energy_production_tomorrow_11') | float(default=0)) | round(2) }}
        unit_of_measurement: "kWh"
        device_class: energy
        attributes:
          watts: >-
           {% set sensor1 = state_attr('sensor.energy_production_tomorrow_8', 'watts') %}
           {% set sensor2 = state_attr('sensor.energy_production_tomorrow_9', 'watts') %}
           {% set sensor3 = state_attr('sensor.energy_production_tomorrow_10', 'watts') %}
           {% set sensor4 = state_attr('sensor.energy_production_tomorrow_11', 'watts') %}
            {% set ns = namespace(output={}) %}
            {% for time, value in sensor1.items() %}
              {% set sum_value = value + sensor2 + sensor3 + sensor4[time] %}
              {% set ns.output = dict({time: sum_value}, **ns.output) %}
            {% endfor %}
            {{ ns.output }}
          wh_period: >-
           {% set sensor1 = state_attr('sensor.energy_production_tomorrow_8', 'wh_period') %}
           {% set sensor2 = state_attr('sensor.energy_production_tomorrow_9', 'wh_period') %}
           {% set sensor3 = state_attr('sensor.energy_production_tomorrow_10', 'wh_period') %}
           {% set sensor4 = state_attr('sensor.energy_production_tomorrow_11', 'wh_period') %}
            {% set ns = namespace(output={}) %}
            {% for time, value in sensor1.items() %}
              {% set sum_value = value + sensor2 + sensor3 + sensor4[time] %}
              {% set ns.output = dict({time: sum_value}, **ns.output) %}
            {% endfor %}
            {{ ns.output }}