Skip to content

Instantly share code, notes, and snippets.

@svinz
Last active December 30, 2022 19:47
Show Gist options
  • Save svinz/2c5f0231d39315f1d25b3e188cacb332 to your computer and use it in GitHub Desktop.
Save svinz/2c5f0231d39315f1d25b3e188cacb332 to your computer and use it in GitHub Desktop.
Visualize a Powersaver Heat capacitor node in Home Assistant with Apex charts

Introduction

With this example, you can visualize the setpoint from the Heat capacitor node and the electricity price in Home Assistant

Pre-requisites

You need the following installed before you can use this example:

Installation

In Node-RED, select Import from the menu, and paste the code for nodes below. Connect them like this:

[{"id":"dc013b2573667c36","type":"tab","label":"Heatcapacitor to apexchart","disabled":false,"info":"","env":[]},{"id":"9b380d597ac405bd","type":"api-current-state","z":"dc013b2573667c36","name":"Read Nord Pool","server":"ade5bab2.7713c8","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.nordpool_kwh_bergen_nok_3_095_025","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","x":320,"y":100,"wires":[["5a8fb67d458b8f29"]]},{"id":"2adcd75f2e479ad1","type":"inject","z":"dc013b2573667c36","name":"","props":[{"p":"payload"}],"repeat":"3600","crontab":"","once":false,"onceDelay":"10","topic":"","payload":"","payloadType":"date","x":130,"y":100,"wires":[["9b380d597ac405bd"]]},{"id":"5a8fb67d458b8f29","type":"ps-receive-price","z":"dc013b2573667c36","name":"Price Receiver","x":500,"y":100,"wires":[["2859147afa632a19"]]},{"id":"2859147afa632a19","type":"ps-strategy-heat-capacitor","z":"dc013b2573667c36","name":"Heat capacitor","timeHeat1C":50,"timeCool1C":50,"maxTempAdjustment":0.5,"boostTempHeat":0,"boostTempCool":0,"minSavings":0.08,"setpoint":23,"x":700,"y":100,"wires":[[],[],["ca913fa97c0bcd50"]]},{"id":"432829171bc7b018","type":"ha-sensor","z":"dc013b2573667c36","name":"Info from PS to HA","entityConfig":"e4921b906cc4eb37","version":0,"state":"payload","stateType":"str","attributes":[{"property":"Schedule","value":"payload.schedule","valueType":"msg"}],"inputOverride":"allow","outputProperties":[],"x":1170,"y":120,"wires":[[]]},{"id":"ca913fa97c0bcd50","type":"function","z":"dc013b2573667c36","name":"calculate schedule","func":"function addMinutes(date, minutes) {\n return new Date(date.getTime() + minutes * 60 * 1000);\n}\n\n// Get variables from the payload\nconst scheduleStartTime = new Date(msg.payload.startAt);\nconst prices = msg.payload.priceData;\nconst fixed_setpoint = msg.payload.config.setpoint;\nconst adjustments = msg.payload.temperatures;\n\n\nlet schedule = []\nlet last_sp = null;\nlet last_price = null;\n\nfor (let i = 0; i < adjustments.length; i++) {\n let startAt = addMinutes(scheduleStartTime, i);\n let hourDiff = Math.floor((startAt.getTime() - scheduleStartTime.getTime()) / (60 * 60 * 1000))\n\n // Skip this adjustment if nothing changed\n if ((prices[hourDiff].value == last_price) && (adjustments[i] == last_sp)) {\n continue;\n }\n last_sp = adjustments[i];\n last_price = prices[hourDiff].value;\n\n // Add data to schedule\n schedule.push({\n \"startAt\": startAt,\n \"price\": prices[hourDiff].value,\n \"adjustment\": adjustments[i],\n \"setpoint\": fixed_setpoint + adjustments[i]\n });\n}\n\n// What is the setpoint now?\nconst now = new Date();\nconst minutes_since_start = Math.floor((now.getTime() - scheduleStartTime.getTime()) / (60 * 1000));\nconst setpoint_now = fixed_setpoint + adjustments[minutes_since_start];\n\nmsg = {\n \"payload\": {\n \"setpoint_now\": setpoint_now,\n \"schedule\": schedule\n }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":120,"wires":[["432829171bc7b018"]]},{"id":"ade5bab2.7713c8","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true},{"id":"e4921b906cc4eb37","type":"ha-entity-config","server":"ade5bab2.7713c8","deviceConfig":"","name":"toApexChart","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"toApexChart"},{"property":"icon","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""}],"resend":false,"debugEnabled":false}]

Add a custom card with the apex chart with the following code:

type: custom:apexcharts-card
header:
  show: true
  title: HeatCapacitor visualization
now:
  show: true
  label: 
graph_span: 2d
span:
  start: day
apex_config:
  stroke:
    width: 2
  dataLabels:
    enabled: true
  fill:
    type: gradient
    gradient:
      shadeIntensity: 1
      inverseColors: false
      opacityFrom: 0.45
      opacityTo: 0.05
      stops:
        - 10
        - 50
        - 75
        - 1000
  legend:
    show: false
  yaxis:
    - id: setpoint
      decimalsInFloat: 1
      min: auto
      forceNiceScale: true
    - id: price
      show: true
      decimalsInFloat: 1
      extend_to: end
      opposite: true
series:
  - entity: sensor.toapexchart
    name: Setpunkt
    yaxis_id: setpoint
    type: area
    curve: stepline
    stroke_width: 2
    opacity: 0
    color: '#FF0000'
    extend_to: false
    data_generator: |
      return entity.attributes.schedule.map((entry) => {
        return [new Date(entry.startAt), entry.setpoint];
      });
  - entity: sensor.toapexchart
    name: Price
    yaxis_id: price
    type: area
    curve: stepline
    stroke_width: 2
    opacity: 0
    color: '#00FF00'
    extend_to: false
    show:
      in_header: false
      legend_value: false
    float_precision: 4
    data_generator: |
      return entity.attributes.schedule.map((entry) => {
        return [new Date(entry.startAt), entry.price];
      });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment