EV charging scheduling solution with solar power and a home battery

I’ve seriously invested in solar power to charge my EV (a Polestar 2). I’ve used home assistant and node red to automate and monitor my home charging solution.

My current situation:

  • 22 kWh solar panels in 3 orientations
  • 2 x 10 kWh GoodWe ET hybrid inverters
  • 2 x 22,08 kWh BYD HVM batteries
  • Polestar 2, about 30 kWh needed for daily commute
  • A Wallbox pulsar plus charger
  • 8 kWh a day household consumption
  • Dynamic electricity prices. They change hourly.

A few weeks of fiddling around I ended up with this (phone view):

The idea is the following:

  • Status of the charge switch, home battery and charger
  • automatic schedule for switching to the default weekday / weekend strategy
  • charging at night only, option next to is ignore all automation and charge when connected
  • charge from battery (= ignore electricity price) or only charge when the battery is full. At the bottom of this section you can set the % it needs to keep for home use.
  • The bottom is the charge from grid strategy. I use the nordpool_diff integration with an API connection coming from the ENTSO-e Transparency Platform Energy Prices integration. I use a 6 hour interval and a 20 hour interval entity coming from the nordpool_diff integration (see their docs on how to do it)

Here is the code for the dashboard:

title: Laden
views:
  - theme: noctis
    path: default_view
    title: Home
    badges: []
    cards:
      - type: vertical-stack
        title: Status
        cards:
          - square: true
            columns: 2
            type: grid
            cards:
              - type: entity
                entity: input_boolean.charge
                state_color: true
                name: Status laden
              - type: gauge
                entity: sensor.battery_state_of_charge
                name: Status Thuisbatterij
                severity:
                  red: 0
                  yellow: 15
                  green: 50
                needle: false
          - type: entity
            entity: sensor.wallbox_portal_status_description
            name: Status lader
      - type: vertical-stack
        title: Laadschema
        cards:
          - show_name: true
            show_icon: true
            type: button
            tap_action:
              action: toggle
            entity: input_boolean.automatic_charging_schedule
            name: Automatisch schema
            show_state: true
          - square: true
            columns: 2
            type: grid
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: input_boolean.charge_at_night_schedule
                name: Enkel 's nachts laden
                show_state: true
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: input_boolean.charge_when_connected
                name: Direct laden, opties negeren
                show_state: true
      - type: vertical-stack
        title: Instellingen thuisbatterij
        cards:
          - square: true
            columns: 2
            type: grid
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: input_boolean.only_charge_from_battery
                show_state: true
                name: Enkel van de thuisbatterij laden
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: input_boolean.only_charge_with_full_battery
                show_state: true
                name: Enkel laden wanneer de thuisbatterij vol is
          - type: entities
            entities:
              - entity: input_number.stop_charging_at_battery_percentage
                name: Stop met laden bij
      - square: true
        columns: 2
        type: grid
        title: Laden van grid
        cards:
          - show_name: true
            show_icon: true
            type: button
            tap_action:
              action: toggle
            entity: input_boolean.charge_short_interval
            name: Goedkoopste uren van een interval van 6 uur
            show_state: true
          - show_name: true
            show_icon: true
            type: button
            tap_action:
              action: toggle
            entity: input_boolean.charge_long_interval
            show_state: true
            name: Goedkoopste uren van een interval van 20 uur

All the entities used on the dashboard are helpers. Most are input_booleans and the slider is a number.

I use node red for the automation. There are 3 flows:

  • Charge logic
  • Wallbox charger control
  • Automatic schedule

The charge logic is a bit challenging. I don’t want the charger switching on / off all the time (it’s bad for the car battery and the home battery) so I need some kind of hysteresis to avoid that.
The Wallbox integration runs via an API which isn’t reliable. It can become unavailable several times a day. The same thing happens with the GoodWe inverters. They are right on the edge of my wifi range and I’m waiting for the parts for a physical connection. I need the automation to be reliable and be able to cope with connections dropping.

The idea behind the automation is to turn the charger off by a 5s timer. If the logic turns the charger on it sends a reset to the delay timer preventing it from turning off the charge entity. I do this to avoid the entity turning to off for a few milliseconds and then turning on again.
Once the Wallbox charger is connected I check the state of the “charge when connected” and just turn it on when this boolean is set to on. After that I just run through all the boolean inputs and check the conditions. Feel free to ask questions if some of the logic is not very clear.

The other flows turn the charger on or off based on the charge on/of boolean. I used a 30s inject because I found the state_changed node to be unreliable. For some reason it got stuck in “running” mode without triggering.

1 Like

Charge logic:

[{"id":"3e69cdaf73117960","type":"tab","label":"Charge on logic","disabled":false,"info":"","env":[]},{"id":"640730e15ac8308f","type":"api-current-state","z":"3e69cdaf73117960","name":"","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge_at_night_schedule","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":310,"y":280,"wires":[["d5e983d821b83bad"],["37fe8108339c41c4","8adb93a631a3cbe8","8a47a19ca145f6c3","31ce5a6452e8a6a6"]]},{"id":"edac062ec8c65a2e","type":"api-current-state","z":"3e69cdaf73117960","name":"Battery >=?%","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"$number(\t   $entities(\"input_number.stop_charging_at_battery_percentage\").state\t)\t","halt_if_type":"jsonata","halt_if_compare":"gte","entity_id":"sensor.battery_state_of_charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":620,"y":580,"wires":[["69ae13bada2c89ec"],[]]},{"id":"d5e983d821b83bad","type":"time-range-switch","z":"3e69cdaf73117960","name":"19:00-07:00","lat":"51.16054","lon":"4.6485","startTime":"19:01","endTime":"07:01","startOffset":0,"endOffset":0,"x":670,"y":280,"wires":[["37fe8108339c41c4","8adb93a631a3cbe8","8a47a19ca145f6c3","31ce5a6452e8a6a6"],["84266b72acf4a5db"]]},{"id":"8a47a19ca145f6c3","type":"api-current-state","z":"3e69cdaf73117960","name":"","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.only_charge_from_battery","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":250,"y":580,"wires":[["edac062ec8c65a2e"],[]]},{"id":"514bb4f2744ee319","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1070,"y":620,"wires":[["c0b5d5d30dfbbd83"]]},{"id":"31ce5a6452e8a6a6","type":"api-current-state","z":"3e69cdaf73117960","name":"","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.only_charge_with_full_battery","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":320,"y":700,"wires":[["9b24b931347d780c"],[]]},{"id":"9b24b931347d780c","type":"api-current-state","z":"3e69cdaf73117960","name":"Battery >=96%","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"96","halt_if_type":"num","halt_if_compare":"gte","entity_id":"sensor.battery_state_of_charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":680,"y":700,"wires":[["c53bebd0c6bc9449"],[]]},{"id":"2be8becef398085f","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1190,"y":780,"wires":[["c0b5d5d30dfbbd83"]]},{"id":"615e4fcebe049208","type":"api-current-state","z":"3e69cdaf73117960","name":"","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge_when_connected","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":320,"y":180,"wires":[["a34d2937afd18c16"],[]]},{"id":"a34d2937afd18c16","type":"api-current-state","z":"3e69cdaf73117960","name":"Wallbox Unavailable?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"Unavailable","halt_if_type":"str","halt_if_compare":"is","entity_id":"switch.wallbox_portal_pause_resume","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":680,"y":180,"wires":[[],["40fb6d6ab472f564"]]},{"id":"40fb6d6ab472f564","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":910,"y":180,"wires":[["c0b5d5d30dfbbd83"]]},{"id":"37fe8108339c41c4","type":"api-current-state","z":"3e69cdaf73117960","name":"","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge_long_interval","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":290,"y":420,"wires":[["316fff4bc9f3f0ba"],[]]},{"id":"316fff4bc9f3f0ba","type":"api-current-state","z":"3e69cdaf73117960","name":"Interval 20 > 0.5?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"0.5","halt_if_type":"num","halt_if_compare":"gt","entity_id":"sensor.nordpool_diff_interval_20","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":630,"y":420,"wires":[["00e38cfb26f19e37"],[]]},{"id":"00e38cfb26f19e37","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":930,"y":420,"wires":[["c0b5d5d30dfbbd83"]]},{"id":"025eba556e0f115c","type":"inject","z":"3e69cdaf73117960","name":"Inject 30s","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"30","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"","payloadType":"date","x":130,"y":80,"wires":[["afe72b3375754982"]]},{"id":"afe72b3375754982","type":"delay","z":"3e69cdaf73117960","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":300,"y":80,"wires":[["b354759ee335037d"]]},{"id":"8adb93a631a3cbe8","type":"api-current-state","z":"3e69cdaf73117960","name":"","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge_short_interval","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":290,"y":480,"wires":[["9daaeb0eb34917f7"],[]]},{"id":"9daaeb0eb34917f7","type":"api-current-state","z":"3e69cdaf73117960","name":"Interval 6 > 0?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"0","halt_if_type":"num","halt_if_compare":"gt","entity_id":"sensor.nordpool_diff_interval_6","state_type":"num","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":620,"y":480,"wires":[["8f0d81d271476fc4"],[]]},{"id":"8f0d81d271476fc4","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":950,"y":480,"wires":[["c0b5d5d30dfbbd83"]]},{"id":"a65f19fbcded465a","type":"delay","z":"3e69cdaf73117960","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1020,"y":80,"wires":[["9bf4f1e8a804fa95"]]},{"id":"c0b5d5d30dfbbd83","type":"change","z":"3e69cdaf73117960","name":"","rules":[{"t":"set","p":"reset","pt":"msg","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1340,"y":320,"wires":[["a65f19fbcded465a"]]},{"id":"84266b72acf4a5db","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge off","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":870,"y":340,"wires":[[]]},{"id":"b354759ee335037d","type":"api-current-state","z":"3e69cdaf73117960","name":"Wallbox Ready? (= not connected)","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"Ready","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.wallbox_portal_status_description","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":540,"y":80,"wires":[["a25411b7fce0f702"],["a65f19fbcded465a","615e4fcebe049208","640730e15ac8308f"]]},{"id":"a25411b7fce0f702","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge off","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":930,"y":20,"wires":[[]]},{"id":"69ae13bada2c89ec","type":"api-current-state","z":"3e69cdaf73117960","name":"Charge is off?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":740,"y":640,"wires":[["5d112a199befc150"],["514bb4f2744ee319"]]},{"id":"5d112a199befc150","type":"api-current-state","z":"3e69cdaf73117960","name":"Battery >=27%","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"$number(\t   $entities(\t       \"input_number.stop_charging_at_battery_percentage\"\t   ).state\t)+7","halt_if_type":"jsonata","halt_if_compare":"gte","entity_id":"sensor.battery_state_of_charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":860,"y":560,"wires":[["397e79a20c316e03"],[]]},{"id":"397e79a20c316e03","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1050,"y":560,"wires":[["c0b5d5d30dfbbd83"]]},{"id":"c53bebd0c6bc9449","type":"api-current-state","z":"3e69cdaf73117960","name":"Charge is off?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":760,"y":760,"wires":[["645bf126dd182a76"],["2be8becef398085f"]]},{"id":"645bf126dd182a76","type":"api-current-state","z":"3e69cdaf73117960","name":"Battery >=99%","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"99","halt_if_type":"num","halt_if_compare":"gte","entity_id":"sensor.battery_state_of_charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":940,"y":700,"wires":[["516ae73dca691dde"],[]]},{"id":"516ae73dca691dde","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1150,"y":700,"wires":[["c0b5d5d30dfbbd83"]]},{"id":"9bf4f1e8a804fa95","type":"api-call-service","z":"3e69cdaf73117960","name":"Charge off","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.charge"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1230,"y":80,"wires":[[]]},{"id":"f610b696.ce7818","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}]

Wallbox switch:

[{"id":"3de22918c63c2f60","type":"tab","label":"Wallbox on/off","disabled":false,"info":"","env":[]},{"id":"f14b893e615127cf","type":"api-current-state","z":"3de22918c63c2f60","name":"Aan het laden?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"switch.wallbox_portal_pause_resume","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"10","forType":"num","forUnits":"milliseconds","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":620,"y":120,"wires":[[],["cf1458bd1c027aca"]]},{"id":"cf1458bd1c027aca","type":"switch","z":"3de22918c63c2f60","name":"Unavailable? Wallbox","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"unavailable","vt":"str"},{"t":"neq","v":"unavailable","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":840,"y":120,"wires":[[],["bbfd23f57d3439e0"]]},{"id":"1acbd3f2406d3620","type":"api-current-state","z":"3de22918c63c2f60","name":"Aan het laden?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"switch.wallbox_portal_pause_resume","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"10","forType":"num","forUnits":"milliseconds","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":620,"y":180,"wires":[["65825307f9fa0f3a"],[]]},{"id":"65825307f9fa0f3a","type":"api-call-service","z":"3de22918c63c2f60","name":"Wallbox off","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"switch","service":"turn_off","areaId":[],"deviceId":[],"entityId":["switch.wallbox_portal_pause_resume"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":950,"y":200,"wires":[[]]},{"id":"bbfd23f57d3439e0","type":"api-call-service","z":"3de22918c63c2f60","name":"Wallbox on","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"switch","service":"turn_on","areaId":[],"deviceId":[],"entityId":["switch.wallbox_portal_pause_resume"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1050,"y":120,"wires":[[]]},{"id":"a7b451b6423faa0f","type":"delay","z":"3de22918c63c2f60","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":420,"y":120,"wires":[["f14b893e615127cf"]]},{"id":"9df9726fe6b73087","type":"delay","z":"3de22918c63c2f60","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":440,"y":180,"wires":[["1acbd3f2406d3620"]]},{"id":"a10f4cde57790669","type":"inject","z":"3de22918c63c2f60","name":"30s","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"30","crontab":"","once":true,"onceDelay":"10","topic":"","payload":"","payloadType":"date","x":90,"y":160,"wires":[["4a1d3639ab25fd14"]]},{"id":"4a1d3639ab25fd14","type":"api-current-state","z":"3de22918c63c2f60","name":"Charge on/off?","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":240,"y":160,"wires":[["a7b451b6423faa0f"],["9df9726fe6b73087"]]},{"id":"f610b696.ce7818","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}]

Schedule:

[{"id":"aeeb910c928b1184","type":"tab","label":"Automatic charging schedule","disabled":false,"info":"","env":[]},{"id":"db307551baf8511c","type":"inject","z":"aeeb910c928b1184","name":"Inject weekday 1/min","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"*/1 0-23 * * 1,2,3,4,5","once":true,"onceDelay":"5","topic":"","payload":"","payloadType":"date","x":200,"y":180,"wires":[["327f7c958e782bb9"]]},{"id":"c726b31e319cbd0c","type":"inject","z":"aeeb910c928b1184","name":"Inject weekend 1/min","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"*/1 0-23 * * 6,0","once":true,"onceDelay":"1","topic":"","payload":"","payloadType":"date","x":220,"y":460,"wires":[["2fab2cf710156d8b"]]},{"id":"d8fd2f88530ff283","type":"api-current-state","z":"aeeb910c928b1184","name":"Automatic schedule on","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.automatic_charging_schedule","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"10","forType":"num","forUnits":"seconds","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":200,"y":240,"wires":[["142777d01278f080","0d547b5d64084f7b"],[]]},{"id":"327f7c958e782bb9","type":"delay","z":"aeeb910c928b1184","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":400,"y":180,"wires":[["d8fd2f88530ff283"]]},{"id":"142777d01278f080","type":"api-call-service","z":"aeeb910c928b1184","name":"Off: long interval, when connected, full battery, only from battery","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.charge_long_interval","input_boolean.charge_when_connected","input_boolean.only_charge_from_battery","input_boolean.only_charge_with_full_battery"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":570,"y":240,"wires":[[]]},{"id":"0d547b5d64084f7b","type":"api-call-service","z":"aeeb910c928b1184","name":"On: at night, short interval","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.charge_short_interval","input_boolean.charge_at_night_schedule"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":450,"y":300,"wires":[[]]},{"id":"2fab2cf710156d8b","type":"delay","z":"aeeb910c928b1184","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":440,"y":460,"wires":[["047d514c94b511f8"]]},{"id":"047d514c94b511f8","type":"api-current-state","z":"aeeb910c928b1184","name":"Automatic schedule on","server":"f610b696.ce7818","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.automatic_charging_schedule","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"10","forType":"num","forUnits":"seconds","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":240,"y":520,"wires":[["8f5178e93efba710","dbd773f441881490"],[]]},{"id":"8f5178e93efba710","type":"api-call-service","z":"aeeb910c928b1184","name":"Off: long interval, short interval, when connected, at night, only from battery","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.charge_long_interval","input_boolean.charge_short_interval","input_boolean.charge_when_connected","input_boolean.charge_at_night_schedule","input_boolean.only_charge_from_battery"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":650,"y":520,"wires":[[]]},{"id":"dbd773f441881490","type":"api-call-service","z":"aeeb910c928b1184","name":"On: Only charge with full battery","server":"f610b696.ce7818","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.only_charge_with_full_battery"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":510,"y":580,"wires":[[]]},{"id":"f610b696.ce7818","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}]

Remarks, advice, questions, … feel free to reply!

2 Likes

Great project - question, do you factor in the cost of a cycle of your battery? Eg. - assuming your battery has an economic life of 6000 cycles do you take account of the usage cost? My battery will arrive in 2 month time; I intend to build similar logic (situation: 60kwh car battery, 15kwh commute, 15kwh life4po batteries/12kw power, 7kwp solar panels; wallbox pulsar 11kw; dynamic contract).

Based on energy prices I want to charge the battery and off load when prices exceed 15c of purchase cost (roughly the cost per kwh assuming 6000 cycles economic lifetime and 95% battery efficiency). Don’t intend to charge the car from the home battery - seems inefficient and makes to me more sense to offload to grid at the highest rate and charge the car directly

How many hours do you select in each of these intervals of 6 and 20hours? 2? 3? Can you share the code of these two booleans? Not completely sure what you are testing against >0 and >0.5 respectively for these two bools.

So far spend my time optimizing green charging where I adjust the A of the wallbox from 6-16 depending on excess solar. All in HA automations though. What’s the advantage of node red in your setup?

A lot of questions you have here:

Battery liftime: Number of cycles is a nice commercial way to show battery lifetime, but when you look at the warranty terms and conditions you’ll see most of the brands use thoughput, so I made two sensors to track it. The numbers come from the BYD warranty conditions, they warrant at least 60% capacity after 68,310MWh thoughput:

 template:
    - sensor:
       - name: "battery_lifetime"
          unit_of_measurement: "%"
          state: >
            {% set current_charge = states('sensor.total_battery_discharge') | float %}
            {{ ((1 - (current_charge / 68310)) * 100) | float(100) | round(2)}}
        - name: "battery_minimum_charge_left"
          unit_of_measurement: "kWh"
          state: >
            {% set lifetime = states('sensor.battery_lifetime') | float %}
            {% set degradation = 0.4 | float %}
            {% set maximum_charge = 22.08 | float %}
            {{ (((1 - degradation) + (degradation * (lifetime / 100))) * maximum_charge) | float | round(2)}}

Battery use: It would be easy to track the costs, but I don’t take it into account in an automation. I decided not to use peak shaving or to load/unload from/to grid using the battery. For me, the costs just don’t add up.
In winter I can charge at night when the costs are low. During the other months the solar production should be sufficient and I can charge from battery.

Hour control: I use the GitHub - jpulakka/nordpool_diff: Transforms electricity spot price into thermostat control signal. Home Assistant custom component. integration of which I use the interval sensor. For example:

sensor:
  - platform: nordpool_diff
    entsoe_entity: sensor.injectie_current_electricity_market_price
    nordpool_entity: sensor.nordpool_kwh_be_eur_5_10_0
    filter_length: 3
    filter_type: interval
    unit: ''
    normalize: 'no'

It just looks forward and compares the current price the the price for the next xx hours and sets the price difference (not the absolute value) on a scale from -1 to +1, so the >0 option in the automation means the current price is in the cheapest half for the next xx hours. It’s explained in more detail in the automations readme.
Since the prices usually rise towards the morning peak the car usually is able to charge for 3 to 5 hours each night.

Using node red appears to be more conveniet for me since it’s a visual too.

3 Likes

@Bel_RV How did you integrate the BYD batteries into HA?

I am still looking for the “best” batteries that can be integrated in HA.
AlphaESS has nice battery solutions and they presumably can be managed via modbus.

@bartland I don’t communicate with the batteries directly but through a goodwe ET inverter. The standard goodwe integration exposes 149 entities to HA, a lot of which are of the battery. There is also a custom integration with even more options.

Very nice project! thanks for sharing. I am not sure if I fully understand the solution yet.
my main question for now is how/what kind of actions do you send to the inverter to (for example) start charging the battery? is this within the node red code you shared?
Is that a matter of installing the goodWe integration into HA and then be able to send actions to the device through automations or node red? just wondering how that code looks like

@Joost5000 The idea is that Goodwe handles the power distribution just fine according to the operation mode of the inverter: General, Backup, Off grid or Eco mode. Recently Goodwe also added a peak shaving mode which is available in HA using the custom Goodwe inegration: GitHub - mletenay/home-assistant-goodwe-inverter: Experimental version of Home Assistant integration for Goodwe solar inverters

So, sinceGoodwe handles the battery operation, you just take care of you power management based on parameters you find important for your situation.

The charging logic matured a bit, so I have slightly different options and a modified logic flow from what you see above, but the idea remains the same.

This is how it looks like at the moment:

FYI, last month I saved >€500 of my electricity bill by using this solution. December was less profitable due to a very high peak in the electricity prices and ended up slightly above €100 savings.

1 Like

So atm you’re not controlling the charging of the BYD batteries behind your inverter themselves, I’m guessing?

@Thibaultmol No I don’t. When there is excess power available they will charge untill they are full.
Goodwe doesn’t have the option to control the betteries directly. I can’t really think of a good reason why I should be able to?

Dynamic tarrif I guess?
being able to stop charging the battery right before prices go negative so put as much as possible to the grid and finish charging from the grid/pv when prices are negative? I dunno

These gains are marginal. When prices go negative you still need to pay distribution costs which often makes them positive again. So far this year only a few hours have been negative including distribution costs.
At times when prices are negative, your battery is almost always full since the prices are that low because there is an excess of solar power.

1 Like

I keep a detailed recored of what I actually pay for my electricity and what I would pay if I would use a normal contract without automation. Current savings for this year are >€2000 for the first 7 months of 2023.

Hi,

Where do you get you sensor.INJECTIE sensor from ??? can’t find it in the entsoe integration?

Thx in advance !

Kr,

Bart

Thats the name het uses. You can replace it with your entso-e sensor (probably the one with current in the name)

The entso-e sensors don’t have an injection sensor within the integration. That’s why I’m asking

Who sets /determines the injection prices? If these are EPEX then just setup a second entso-e which gives you the prices subject to different rules (eg excluding vat or other taxes).

I’m very interested in your approach and especially the use of nordpool_diff looks like it could solve a use case I have around scheduling my HVAC to anticipate rising prices.

I’m not located in the EU but my energy prices change every 5 minutes so I would need to translate the nordpool_diff concept to my provider Amber Electric.

I’m doing similar scheduling and optimisation for my EV, home battery, HVAC hot water and other household loads using EMHASS add-on: An energy management optimization add-on for Home Assistant OS and supervised

The results have been amazing and like you I am saving thousands of dollars per year against the base case.

2 Likes

What kind of program is it (logic)? How do you use it (Script?)?

Very nice!

I’ve been trying to do something similar for close to two years but haven’t gotten anywhere near as far as you. Main reason is my inability to understand programming logic.

I’ve done what I can using the UI tools and automations and currently I can control discharge by setting discharge allowed intervals manually. One thing I haven’t figured out how to do is to control the home battery charging. I solved it by setting my inverter to charge between specific hours during the night when it’s most likely to be low electricity price but it’s very coarse. How did you configure home assistant to tell inverter to charge battery? I can’t find any entity that can be set to do that. Changing operation mode doesn’t work either as the charging times doesn’t activate unless you set them through solar go app.

Would highly appreciate if you could share more details on the controlling entities in HA.

Cheers
Anders