diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb85aed --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# Boiler Control - AppDaemon + +AppDaemon script that optimizes when to power (heat up) the boiler with respect to the hourly (dynamic) electricity prices. + +## Configuration +Copy boilercontrol.py to your appdaemon config `/apps` folder. + +In `apps.yaml`, activate the app by adding something like this: +```yml + boiler_control: + module: boilercontrol + class: BoilerControl + # Switch entity that turns on/off the boiler + boiler_switch: switch.boiler_switch_0 + # Sensor entity which contains the current dynamic electricity prices. This sensor should have an attribute "raw_today", which contains a dictionary with the prices per hour + price_sensor: sensor.nordpool + # (Optional) sensor to add to home assistant which contains the output of the optimization script + output_sensor: sensor.boiler_control_output + + # Minimal number of hours the boiler should be powered per day + boiler_on_for_hours: 6 + # The algorithm splits the day up into equal size "periods", and tries to ensure the boiler gets at least one hour of power during each period. + # This variable controls how many periods of boiler activity each day should have + boiler_reheat_periods_per_day: 4 +``` + +You can use [ApexCharts](https://github.com/RomRider/apexcharts-card) to display to output of the algorithm. Config: +```yml +type: custom:apexcharts-card +graph_span: 24h +header: + title: Energy price today (€/kWh) + show: true +span: + start: day +now: + show: true + label: Now +series: + - entity: sensor.nordpool + float_precision: 3 + type: column + data_generator: | + return entity.attributes.raw_today.map((start, index) => { + return [new Date(start["start"]).getTime() + 30 * 1000 * 60, entity.attributes.raw_today[index]["value"]]; + }); + - entity: sensor.boiler_control_output + float_precision: 0 + name: Boiler controller + type: line + curve: stepline + stroke_width: 2 + data_generator: | + const val = entity.attributes.average_price + return [[0,0], ...entity.attributes.merged_blocks.flatMap(b => { + return [[new Date(b["start"]).getTime(), val], [new Date(b["end"]).getTime(), 0]] + })] +yaxis: + - min: 0 + decimals: 2 +``` diff --git a/boilercontrol.py b/boilercontrol.py index 0e0970b..cf760ae 100644 --- a/boilercontrol.py +++ b/boilercontrol.py @@ -78,7 +78,7 @@ class BoilerControl(hass.Hass): self.run_at(self.turn_boiler_off, block["end"]) if (self.output_sensor != None): - self.set_state(self.output_sensor, state="on" if currently_on else "off", attributes={"active_blocks": boiler_active_blocks, "merged_blocks": merged_blocks}) + self.set_state(self.output_sensor, state="on" if currently_on else "off", attributes={"active_blocks": boiler_active_blocks, "merged_blocks": merged_blocks, "average_price": sum([block["value"] for block in boiler_active_blocks]) / len(boiler_active_blocks)}) def turn_boiler_on(self, cb_args=None):