[Windcentrale] Cumulative month graph in HA

The Dutch Windcentrale app has stopped working due to a new API. More generic learnings for larger audience:

  • Create cumulative SQL result
  • Get multi-result SQL query data to HA sensor, and plot with ApexCharts
  • Create static benchmark series sensor

UPDATED, SEE NEW POST BELOW

My favorite graph (in the now defunct app) is this monthly cumulative graph, with benchmark line:


I have mimicked this graph in Home Assistant. I now only hope that @jobvk is able to transform his custom component to the new API :slight_smile:
Result:

Prerequisites

  • ApexCharts (from HACS)
  • Your data in a database. I use LTSS but you could also query the long term statistics database built in HA. I have also applied the state_numeric additional column in the database.
  • NodeRed addon
  • Benchmark data. I have 3 shares; you can scale my prognosis data (see below)

1 Query the database
Data is stored as cumulative per month; it resets every month. What we need is cumulative per year, in monthly buckets. This query picks up the data and sums the maximum values of each month to date:

WITH data AS (
  SELECT DISTINCT ON (date_trunc('month', time)) date_trunc('month', time) as Month_Name, time, state_numeric, MAX(state_numeric)  as maxx
  FROM ltss WHERE entity_id='sensor.het_rode_hert_month_production' AND state_numeric IS NOT  NULL AND  extract (year FROM time) = extract (year FROM CURRENT_DATE)
  GROUP  BY date_trunc('month', time), state_numeric, time
  ORDER  BY date_trunc('month', time),state_numeric DESC
)

SELECT time, SUM(state_numeric) OVER (ORDER BY time ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS state_numeric
FROM data

2 Get the data via Node Red
I am querying the data through Node Red with this flow. It does the following:

  1. Executes the SQL query.
  2. Ads a random number to msg.random
  3. jsonify msg.payload
  4. Sets state of a HA sensor to msg.random and sets attribute data to msg.payload. Reason for this is the limit to number of characters of 256 for states.
NodeRed code [{"id":"2c7a814593cededa","type":"tab","label":"Winddelen","disabled":false,"info":"","env":[]},{"id":"101b5edb1fad37ad","type":"postgresql","z":"2c7a814593cededa","name":"","query":"with data as (\n\n\nSELECT DISTINCT ON (date_trunc('month', time)) \n date_trunc('month', time) as Month_Name, time, state_numeric,\n MAX(state_numeric) as maxx\nFROM ltss WHERE entity_id='sensor.het_rode_hert_month_production' AND state_numeric IS NOT NULL AND \n extract (year FROM time) = extract (year FROM CURRENT_DATE)\n\n GROUP BY date_trunc('month', time), state_numeric, time\nORDER BY date_trunc('month', time),state_numeric DESC)\n\nselect time, sum(state_numeric) over (order by time asc rows between unbounded preceding and current row) as state_numeric\nfrom data","postgreSQLConfig":"36b36b8f4f678a27","split":false,"rowsPerMsg":1,"outputs":1,"x":170,"y":140,"wires":[["e8eb0a23da70f150"]]},{"id":"085f426f3a424c7f","type":"debug","z":"2c7a814593cededa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":470,"y":340,"wires":[]},{"id":"94fcb0d10916e074","type":"inject","z":"2c7a814593cededa","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":240,"wires":[["101b5edb1fad37ad"]]},{"id":"d5ed69168f298e96","type":"ha-entity","z":"2c7a814593cededa","name":"winddelen maandproductie2","server":"c5f56a3a.f3f838","version":2,"debugenabled":true,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":""},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":"x"},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"random","stateType":"msg","attributes":[{"property":"data","value":"payload","valueType":"msg"}],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":520,"y":240,"wires":[[]]},{"id":"815019d70363da9c","type":"json","z":"2c7a814593cededa","name":"","property":"payload","action":"str","pretty":false,"x":290,"y":240,"wires":[["085f426f3a424c7f","d5ed69168f298e96"]]},{"id":"e8eb0a23da70f150","type":"random","z":"2c7a814593cededa","name":"","low":1,"high":"100","inte":"true","property":"random","x":380,"y":140,"wires":[["815019d70363da9c"]]},{"id":"36b36b8f4f678a27","type":"postgreSQLConfig","name":"","host":"192.168.0.5","hostFieldType":"str","port":"5432","portFieldType":"num","database":"ha_ltss","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"homeassistant","userFieldType":"str","password":"","passwordFieldType":"str"},{"id":"c5f56a3a.f3f838","type":"server","name":"Home Assistant","version":2,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30"}]

3 Add Prognosis sensor
Add this to your configuration.yaml. You will need to update the dates yearly. This is for 3 shares; scale for your own situation.

- platform: template
  sensors:
    winddelen_prognose:
      friendly_name: Winddelen Prognose
      unit_of_measurement: 'kWh'
      value_template: 1
      attribute_templates:
        prognose: |
          {{ [
            { "date" : "2022-01-01","value" : 0},
            { "date" : "2022-01-31","value" : 178},
            { "date" : "2022-02-28","value" : 311},
            { "date" : "2022-03-31","value" : 461},
            { "date" : "2022-04-30","value" : 566},
            { "date" : "2022-05-31","value" : 669},
            { "date" : "2022-06-30","value" : 745},
            { "date" : "2022-07-31","value" : 836},
            { "date" : "2022-08-31","value" : 915},
            { "date" : "2022-09-30","value" : 1020},
            { "date" : "2022-10-31","value" : 1150},
            { "date" : "2022-11-30","value" : 1330},
            { "date" : "2022-12-31","value" : 1500}
          ] }}

4 Add graph in HA
Add an ApexCharts card to your dashboard. It extracts data from the prognosis sensor and from the SQL sensor. The latter is de-jsonified in-situ.

type: custom:apexcharts-card
graph_span: 365d
span:
  start: year
header:
  show: false
all_series_config:
  stroke_width: 2
  unit: kWh
apex_config:
  xaxis:
    type: categor
    overwriteCategories:
      - jan
      - feb
      - mar
      - apr
      - mei
      - jun
      - jul
      - aug
      - sep
      - okt
      - nov
      - dec
    tickPlacement: between
    labels:
      datetimeFormatter:
        month: MMM
    tickAmount: 12
  chart:
    background: '#3f8bc9'
    foreColor: '#ffffff'
  markers:
    size: 1
series:
  - entity: sensor.winddelen_prognose
    type: line
    curve: straight
    color: '#999999'
    data_generator: |
      return entity.attributes.prognose.map((entry) => {
            return [new Date(entry.date), entry.value];
          });
  - entity: sensor.nodered_d5ed69168f298e96
    type: line
    curve: straight
    color: '#ffffff'
    extend_to: now
    data_generator: |
      const data = JSON.parse(entity.attributes.data);
      return data.map((entry) => {

            return [new Date(entry.time), entry.state_numeric];
          });

5 To Do

  • Automate retrieval of Windcentrale data
  • Schedule execution of query
  • Find out how to make sensor name in NodeRed pretty
1 Like

@balk77 The graph looks great.

I’m indeed still working on the new APIs.
When I’m done with that. Then I would like to get in touch with you. I would like to create a HACS Lovelace Card with you for the windcentrale custom component.

Let me know if you’re interested in that?

1 Like

sure! Let me know when/what you need. I can also test your custom component if you want me to.

Hi Job, I managed to create some more graphs, based on your new integration.

  1. The energy_management sensor is consumed by some utility_meter sensors at hourly, daily, weekly and monthly buckets.
  2. Install Apexcharts-card
  3. Plot using Apexcharts from Long Term Statistics database. Code below.

day plot:

type: custom:apexcharts-card
graph_span: 1d
span:
  start: day
header:
  show: false
all_series_config:
  stroke_width: 2
  unit: kWh
apex_config:
  title:
    text: Dagproductie
  xaxis:
    labels:
      datetimeFormatter:
        hour: HH
  yaxis:
    title:
      text: Wh
    decimalsInFloat: 0
  chart:
    background: '#3f8bc9'
    foreColor: '#ffffff'
  markers:
    size: 1
  tooltip:
    shared: true
series:
  - entity: sensor.rode_hert_hourly
    type: line
    curve: straight
    color: '#ffffff'
    extend_to: now
    transform: return x * 1000;
    group_by:
      func: max
      duration: 1h
    unit: Wh

Week plot:

type: custom:apexcharts-card
graph_span: 7d
span:
  start: week
  offset: +1d
header:
  show: false
all_series_config:
  stroke_width: 2
  unit: kWh
apex_config:
  title:
    text: Weekproductie
  xaxis:
    tickPlacement: between
    labels:
      format: ddd
    tickAmount: 8
  yaxis:
    title:
      text: kWh
  chart:
    background: '#3f8bc9'
    foreColor: '#ffffff'
  markers:
    size: 1
  tooltip:
    shared: true
series:
  - entity: sensor.rode_hert_daily
    type: line
    curve: straight
    color: '#ffffff'
    extend_to: now
    transform: return x * 1;
    group_by:
      func: max
      duration: 4h
    unit: kWh

Year plot

type: custom:apexcharts-card
graph_span: 365d
span:
  start: year
header:
  show: false
all_series_config:
  stroke_width: 2
  unit: kWh
apex_config:
  title:
    text: Jaarproductie
  yaxis:
    title:
      text: kWh
    decimalsInFloat: 0
  xaxis:
    tickPlacement: between
    labels:
      format: MMM
    tickAmount: 12
  chart:
    background: '#3f8bc9'
    foreColor: '#ffffff'
  markers:
    size: 1
  tooltip:
    shared: true
series:
  - entity: sensor.winddelen_prognose
    type: line
    curve: straight
    color: '#999999'
    data_generator: |
      return entity.attributes.prognose.map((entry) => {
            return [new Date(entry.date), entry.value];
          });
  - entity: sensor.het_rode_hert_energy_management
    type: line
    curve: straight
    color: '#ffffff'
    extend_to: now
    statistics:
      type: state
      period: hour
      align: middle