John_Otters schreef op dinsdag 21 november 2023 @ 15:33:
Ik ben nog wel op zoek naar een betere aansturing van onze setpoint. Begane grond laat ik de thermostaat gewoon z'n ding doen, maar op de verdieping (elke kamer eigen temperatuursensor) moet HA het aansturen via de OTGW. Setpoint staat dan standaard op 35, wat niet altijd nodig is.
Ik ben dus wel benieuwd naar je algortithme.

Het grootste probleem met mijn algorithme is voornamelijk dat het een zooitje is en niet gepackaged is, dus die uitpluizen is nogal een klus, maar hier komt ie:
De hoofdmoot is een HA trigger template
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| - trigger:
- platform: state
entity_id:
- climate.zb_ts0601_thermostat
- climate.otgw_nodemcu_thermostat
- sensor.gaming_temperature_filter
- sensor.garden_sensor_temperature
- input_number.room_compensation_factor
- input_number.climate_gaming_enable
- input_number.climate_living_enable
- input_number.boiler_cs_min_ot
- input_number.boiler_cs_max_ot
- input_number.boiler_cs_min_temp
- input_number.boiler_cs_max_temp
variables:
gaming_temp: "{{ states('sensor.gaming_temperature_filter') | float(default=0.0) }}"
gaming_tgt: "{{ state_attr('climate.zb_ts0601_thermostat', 'occupied_heating_setpoint') / 100 | float(default=0.0) }}"
gaming_enable: "{{ states('input_number.climate_gaming_enable') | int(default=0) }}"
living_temp: "{{ state_attr('climate.otgw_nodemcu_thermostat', 'current_temperature') | float(default=0.0) }}"
living_tgt: "{{ state_attr('climate.otgw_nodemcu_thermostat', 'temperature') | float(default=0.0) }}"
living_enable: "{{ states('input_number.climate_living_enable') | int(default=0) }}"
ot_temp: "{{ states('sensor.garden_sensor_temperature') | float(default=0.0) }}"
rcf: "{{ states('input_number.room_compensation_factor') | float(default=1.0) }}"
Xo: "{{ states('input_number.boiler_cs_min_ot') | float(default=0.0) }}"
Yo: "{{ states('input_number.boiler_cs_max_ot') | float(default=0.0) }}"
Xi: "{{ states('input_number.boiler_cs_min_temp') | float(default=0.0) }}"
Yi: "{{ states('input_number.boiler_cs_max_temp') | float(default=0.0) }}"
Acc: "{{ states('input_number.ch_accumulator') | float(default=0.0) }}"
sensor:
- name: "Boiler CS WDC"
icon: mdi:heating-coil
unit_of_measurement: "°C"
state: >-
{% set gaming_heat_rq = (([(gaming_tgt - gaming_temp), 0] | max) * gaming_enable) | float(default=0.0) | round(1) %}
{% set living_heat_rq = (([(living_tgt - living_temp), 0] | max) * living_enable) | float(default=0.0) | round(1) %}
{% set trigger_tgt = [gaming_heat_rq, living_heat_rq] | max %}
{% set is_enabled = trigger_tgt > 0 %}
{% set ref_temp = [(Yi - ((Xo - ot_temp) | abs * ((Yi - Xi) / (Yo - Xo)) | abs)) + (trigger_tgt * rcf) + Acc | float, Yi] | min | round(1) %}
{% if is_enabled %}
{{ [ref_temp, Xi] | max | float | round(1) }}
{% else %}
{{ 5 | float | round(1) }}
{% endif %}
attributes:
gaming_heat_req: "{{ (([(gaming_tgt - gaming_temp), 0] | max) * gaming_enable) | float(default=0.0) | round(1) }}"
living_heat_req: "{{ (([(living_tgt - living_temp), 0] | max) * living_enable) | float(default=0.0) | round(1) }}"
trigger_temp: "{{ [(gaming_tgt - gaming_temp), (living_tgt - living_temp)] | max | float(default=0.0) | round(1) }}"
absolute_offset: >-
{{ [([(gaming_tgt - gaming_temp), (living_tgt - living_temp)] | max | float(default=0.0)), 0.0] | max | round(2) }}
raw_temperature: >-
{% set gaming_heat_rq = ([(gaming_tgt - gaming_temp), 0] | max) | float(default=0.0) | round(1) %}
{% set living_heat_rq = ([(living_tgt - living_temp), 0] | max) | float(default=0.0) | round(1) %}
{% set trigger_tgt = [gaming_heat_rq, living_heat_rq] | max %}
{{ ((Yi - ((Xo - ot_temp) | abs * ((Yi - Xi) / (Yo - Xo)) | abs)) + (trigger_tgt * rcf) + Acc) | float | round(1) }}
accumulated: "{{ Acc }}"
- name: "CS grade"
state: >-
{{ ((Yi - Xi) / (Yo - Xo) | float) | round(2) }} |
Noot: niet bepaald geschreven om super leesbaar te zijn
Ik heb het opgedeeld in 2 zones: Gaming (dat is mijn kantoor en game ruimte op de begane grond) en Living (uiteraard de woonkamer op de 1e verdieping)
Voor elke zone trek ik 3 datapunten: de huidige temperatuur, de gewenste temperatuur en een binary_sensor (switch in dit geval) die aangeeft of deze ruimte mee mag doen met de warmtevraag. Die switches worden via automations bediend op basis van open ramen en deuren.
ot_temp is de temperatuur die ik van mijn tuinsensor doorkrijg. Daar zit een fallback in verwerkt waarbij bij ontbreken van die meetgegevens de temperatuur verkregen van het KNMI wordt doorgegeven.
rcf is de Room Compensation Factor. Min of meer een vermenigvuldiging die gebruikt wordt om de verwarmingslijn naar boven of beneden bij te stellen.
Xo,Yo,Xi,Yi zijn de vier punten die een lijn vormen op een grafiek. Dit is het hart van de weercompensatie.
Dit bepaalt hoe de lijn loopt bij de verschillende buitentemperaturen. Ik heb hier expres niet geprobeerd een ingewikkelde curve te maken, het was zo al moeilijk genoeg.
Acc is een accumulator. Dit is een input_number die bijhoudt hoe lang we onder of boven de gewenste temperatuur zitten. Hoe langer we onder de gewenste temperatuur blijven, hoe meer deze zal oplopen en de CS temperatuur zal ophogen.
De sensor zelf doet dus een hoop logica en komt uiteindelijk met een waarde van of 5, als er geen warmtevraag is, of met een punt op de stooklijn die overeenkomt met de gecompenseerde buitentemperatuur, vermeerderd met de Accumulator.
Daarnaast gooi ik nog een hoop data in de sensor attributes zodat ik dat in mijn dashboard kan zetten.
Dan het automation gedeelte:
Een aantal automations zijn niet direct gekoppeld aan die sensor: ik heb een automation die de waterpomp aanzet voor 2 minuten elke 24u, en eentje die de thermostaat terugzet naar 20 als iemand het in hun hoofd haalt om die op 23 te laten staan (is gebeurd).
Maar de belangrijkste hier is
Set Central Heating Temperature for boiler.
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
| alias: Set Central Heating temperature for Boiler
description: ""
trigger:
- platform: state
entity_id:
- sensor.boiler_cs_wdc
id: Active
- platform: homeassistant
event: start
id: turn-on
- platform: homeassistant
event: shutdown
id: shutdown
- platform: state
entity_id:
- input_boolean.central_heating_auto_enabled
id: boolean_enable
condition: []
action:
- choose:
- conditions:
- condition: trigger
id:
- turn-on
sequence:
- service: input_boolean.turn_on
metadata: {}
data: {}
target:
entity_id: input_boolean.central_heating_auto_enabled
- conditions:
- condition: trigger
id:
- shutdown
sequence:
- service: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.central_heating_auto_enabled
- stop: Shutting down
- conditions:
- condition: state
entity_id: input_boolean.central_heating_auto_enabled
state: "on"
- condition: or
conditions:
- condition: trigger
id: Active
- condition: trigger
id: boolean_enable
sequence:
- choose:
- conditions:
- condition: template
value_template: >-
{{ (states('sensor.boiler_cs_wdc') | float) | round(1) >
10.0 }}
- condition: state
entity_id: timer.central_heating_cooldown
state: idle
sequence:
- service: input_boolean.turn_on
data: {}
target:
entity_id: input_boolean.ch_last_activity
- service: input_number.set_value
data_template:
value: >-
{% set accumulator =
states('input_number.ch_accumulator') |
float(default=0.0) %} {% set offset =
state_attr('sensor.boiler_cs_wdc', 'absolute_offset') |
float(default=0.0) %} {% set rcf =
states('input_number.room_compensation_factor') |
float(default=0.0) %} {% set target_accu = (offset *
rcf) - accumulator %} {{ ([-1, ( accumulator +
target_accu ) | float(default=0.0) | round(2), 5] |
sort)[1] }}
entity_id: input_number.ch_accumulator
- repeat:
while:
- condition: template
value_template: >-
{{ (states('sensor.boiler_cs_wdc') | float) |
round(1) > 10.0 }}
sequence:
- service: mqtt.publish
data:
topic: OTGW/set/otgw-308398A2F389/ctrlsetpt
payload_template: "{{ states('sensor.boiler_cs_wdc') | float }}"
- service: mqtt.publish
data:
topic: OTGW/set/otgw-308398A2F389/maxrelmodlvl
payload: "100"
qos: "1"
- delay:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
- conditions:
- condition: or
conditions:
- condition: template
value_template: >-
{{ (states('sensor.boiler_cs_wdc') | float) | round(1)
<= 10.0 }}
- condition: state
entity_id: timer.central_heating_cooldown
state: active
sequence:
- service: input_number.set_value
data_template:
value: >-
{{ (states('input_number.ch_accumulator') |
float(default=0.0) * 0.50) | round(2) }}
entity_id: input_number.ch_accumulator
- if:
- condition: state
entity_id: input_boolean.ch_last_activity
state: "on"
then:
- service: timer.start
data:
duration: 0
target:
entity_id: timer.central_heating_cooldown
- service: input_boolean.turn_off
data: {}
target:
entity_id: input_boolean.ch_last_activity
- repeat:
while: []
sequence:
- service: mqtt.publish
data:
topic: OTGW/set/otgw-308398A2F389/ctrlsetpt
payload_template: 5
qos: 0
retain: false
- service: mqtt.publish
data:
topic: OTGW/set/otgw-308398A2F389/maxrelmodlvl
qos: "1"
retain: false
payload: "100"
- delay:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
default:
- service: mqtt.publish
data:
topic: OTGW/set/otgw-308398A2F389/ctrlsetpt
payload: 0
qos: "1"
retain: false
- service: timer.start
data:
duration: 0
target:
entity_id: timer.central_heating_cooldown
- service: input_number.set_value
data:
value: 0
target:
entity_id: input_number.ch_accumulator
mode: restart |
Dit is dus de automation die al die logica gebruikt om de CV aan te sturen. Elke set aan opties heeft een eigen loop en delay van 30 seconden, en met mode: restart voorkom ik dat een loop vast kan gaan lopen.
Hierin zit ook nog een anti-pendel functie verwerkt, op basis van een timer. Zodra de CV uitgaat omdat er geen warmtevraag meer is gaat er een timer lopen van 25 minuten waarbinnen er niet opnieuw gestart kan worden.
Pas als deze verloopt is het weer mogelijk om de CV de warmtevraag te laten verwerken.
Kia E-Niro 2019 Executiveline. OTGW/HA Enthousiasteling.