forked from cenobitedk/esphome_multical402
-
Notifications
You must be signed in to change notification settings - Fork 2
/
multical602.yaml
425 lines (381 loc) · 15.5 KB
/
multical602.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
##########################################################
# ESPHOME CUSTOM COMPONENT #
# FOR #
# KAMSTRUP MULTICAL 402/403 & 602 DISTRICT HEATING METER #
##########################################################
#############################################
# Substitutions section contains all the #
# user/device specific configurable details #
#############################################
substitutions:
device_name: multical602
friendly_name: District Heating
device_description: District Heating Multical 602
platform: ESP8266
board: d1_mini
wifi: !secret wifi_ssid_tp
password: !secret wifi_password_tp
ip: !secret static_ip_multical602
gateway: !secret gateway
ha_api_key: !secret api_key
ap_password: !secret wifi_ap_password
ota_password: !secret multical602_ota_password
# uart_tx: '1' # GPIO1 = D10 - TX pin for hardware UART on ESP8266
# uart_rx: '3' # GPIO3 = D9 - TX pin for hardware UART on ESP8266
uart_tx: '5' # GPIO5 = D1 - Typical TX pin used for software UART on ESP8266
uart_rx: '4' # GPIO4 = D2 - Typical RX pin used for software UART on ESP8266
# uart_tx: '17' # GPIO17 = D27 - Default TX pin for hardware UART#2 on ESP32 modules
# uart_rx: '16' # GPIO16 = D25 - Default RX pin for hardware UART#2 on ESP32 modules
stop_bit: '2' # Set to 2 for Multical 402/403 & 602
update_millis: '300000' # Custom Sensor update interval in milliseconds (5 minutes interval)
# Define your Multical model below with 'REVERSE' true or false statements!
# This will hide unneeded HiRes sensors for other Multical models and not expose these to HA
model_402: 'true' # Set to 'false' if your meter is Multical 402
model_403: 'true' # Set to 'false' if your meter is Multical 403
model_602: 'false' # Set to 'true' if your meter is Multical 402 or 403
#energy_uom: '0.86' # Use this value for energy registers that use kWh as UOM (Multical 403)
energy_uom: '860' # Use this value for energy registers that use MWh as UOM (Multical 602 and 402 ?)
###############################################
# Main YAML block #
# You should not need to edit below this line #
###############################################
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
comment: ${device_description}
platform: ${platform}
board: ${board}
includes:
- kmp.h
- multical402.h
# Upon boot, delta_base will be calculated. delta_base + Energy HiRes register read = calculated Energy HiRes sensor exposed to HA
# Cooling average sensor base values and timestamp will, if possible, be restored from RTC memory (won't survive an ESP power cycle)
# If retained values are unavailable, base values will be reset to current values (energy and volume) and timestamp will be set to .now()
# Various info, warnings, status and errors are written to the log
# Calculation loop will run indefinitely until both delta_base and cooling average sensor base values are calculated
on_boot:
priority: -100
then:
- wait_until:
api.connected:
- delay: 20s
- lambda: |-
ESP_LOGI("custom", "API connect + 20 seconds. Initiate base value calculations");
- while:
condition:
- lambda: |-
return ((id(delta_base) == -1) || (id(cooling_volume_base) == -1 || id(cooling_energy_base) == -1));
then:
- lambda: |-
static int num_executions = 1;
ESP_LOGI("custom", "Base values calculation loop no. #%d", num_executions);
num_executions += 1;
if (!${model_602}) {
ESP_LOGI("custom", "Current delta_base value: %f", id(delta_base));
if ((id(delta_base) == -1) && (!isnan(id(m_energy).raw_state)) && (!isnan(id(m_energy_high).raw_state))) {
id(temp) = roundf(id(m_energy).state * 1000);
id(energy_lowres_2digits) = id(temp) / 1000;
id(delta_base) = id(energy_lowres_2digits) - id(m_energy_high).state;
ESP_LOGI("custom", "delta_base has now been calculated to: %f MWh", id(delta_base));
} else if (id(delta_base) == -1) {
ESP_LOGE("custom", "delta_base value could NOT be calculated. Value is still undefined");
ESP_LOGW("custom", "Wait for %d seconds before attempting delta_base value calculation again", (int(${update_millis})/1000));
}
} else {
ESP_LOGI("custom", "Skip Multical 602 HiRes sensor delta_base calculation");
}
- lambda: |-
if (id(cooling_volume_base) == -1 || id(cooling_energy_base) == -1) {
if ((!isnan(id(m_volume).raw_state)) && (!isnan(id(m_energy).raw_state))) {
id(cooling_volume_base) = id(m_volume).state;
id(cooling_energy_base) = id(m_energy).state;
id(cooling_last_update).publish_state(id(homeassistant_time).now().timestamp);
id(cooling_last_update_retained) = id(cooling_last_update).state;
ESP_LOGW("custom", "One or both cooling base variables were NOT retained! Now both are set to current register values: %f MWh and %f m³", id(cooling_energy_base), id(cooling_volume_base));
} else {
ESP_LOGE("custom", "One or both cooling base variables were NOT set! Current base values: %f MWh and %f m³", id(cooling_energy_base), id(cooling_volume_base));
ESP_LOGW("custom", "Wait for %d seconds before attempting base variable calculations again", (int(${update_millis})/1000));
}
} else {
id(cooling_last_update).publish_state(id(cooling_last_update_retained));
ESP_LOGI("custom", "Cooling base variables were retained. Current values: %f MWh and %f m³", id(cooling_energy_base), id(cooling_volume_base));
}
- delay: !lambda "return ${update_millis};"
# Multical UART
uart:
- id: uart_bus
tx_pin: ${uart_tx}
rx_pin: ${uart_rx}
baud_rate: 1200
data_bits: 8
parity: NONE
stop_bits: ${stop_bit}
#debug:
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: ${ha_api_key}
ota:
password: ${ota_password}
wifi:
ssid: ${wifi}
password: ${password}
fast_connect: true
manual_ip:
static_ip: ${ip}
gateway: ${gateway}
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case WiFi connection fails
ap:
ssid: ${friendly_name} FB Hotspot
password: ${ap_password}
captive_portal:
web_server:
port: 80
# Reset Yearly cooling average sensor base data and timestamp at January 1st, 00.00 hours
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- months: JAN
days_of_month: 1
hours: 0
minutes: 0
seconds: 0
then:
lambda: |-
id(cooling_volume_base) = id(m_volume).state;
id(cooling_energy_base) = id(m_energy).state;
id(cooling_last_update).publish_state(id(homeassistant_time).now().timestamp);
id(cooling_last_update_retained) = id(cooling_last_update).state;
ESP_LOGI("custom", "All base values for Cooling average sensor has been reset. Happy New Year!!");
globals:
# Define delta_base with -1 as initial value. delta_base will be calculated upon ESPHome boot
# If first calculation attempt fails, code will wait for the defined throttle period between register reads and retry once more
# If delta_base can't be calculated, then code will instead continuously return the default low resolution register value
#
# Double type variables needed, as math with floats will not cope with the fraction sizes for the energy registers
- id: delta_base
type: double
restore_value: no
initial_value: "-1"
- id: energy_lowres_2digits
type: double
restore_value: no
initial_value: "0"
- id: temp
type: double
restore_value: no
initial_value: "0"
- id: cooling_volume_base
type: double
restore_value: yes
initial_value: "-1"
- id: cooling_energy_base
type: double
restore_value: yes
initial_value: "-1"
- id: cooling_last_update_retained
type: double
restore_value: yes
initial_value: "0"
# Multical Custom Sensor (${update_millis} contains the update interval in ms; 3600000 is 1 hour)
# Set to 30000 ms = 5 minutes update interval
custom_component:
- lambda: |-
auto multical402 = new Multical402(
${update_millis},
id(uart_bus),
id(m_energy),
id(m_power),
id(m_tin),
id(m_tout),
id(m_tdiff),
id(m_flow),
id(m_volume),
id(m_energy_hires_403),
id(m_energy_high));
App.register_component(multical402);
return {multical402};
components:
- id: multical
binary_sensor:
- platform: status
name: "Node Status"
sensor:
# Uptime sensor / WiFi strength in db and percentage
- name: "Uptime Sensor"
platform: uptime
- name: "WiFi Strength dBm"
platform: wifi_signal
id: wifi_strength_dbm
update_interval: 60s
entity_category: "diagnostic"
# Reports the WiFi signal strength in percentage
- name: "WiFi Strength %"
platform: copy
source_id: wifi_strength_dbm
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "%"
entity_category: "diagnostic"
# Timestamp sensor for last Cooling ability sensor reset
# Sensor is updated via calls from on_boot code when timestamp changes
- name: "Cooling Average Timestamp"
platform: template
id: cooling_last_update
update_interval: never
device_class: timestamp
# Cooling ability sensor that shows average cooling in °C for the consumed volume of district heating water
# In order to get the best tariff rates, some district heating distributors requires the YEARLY average cooling ability to be at least 30°C !
#
# Sensor base values are retained in RTC memory and can survive reflashing and resets on the ESP module
# But the base values won't survive a power cycle reset on the device and will at boot be reset to current energy/volume states.
#
# Note: Energy HiRes sensor update is called from lambda for this sensor
# If your Energy registers UOM is kWh, then change energy_uom value in substitutions from 860 to 0.86
- name: "Cooling Average"
platform: template
id: cooling
update_interval: 60s
icon: "mdi:thermometer"
unit_of_measurement: °C
accuracy_decimals: 2
state_class: "measurement"
device_class: "temperature"
lambda: |-
id(m_energy_hires).update();
double energy_consumed = id(m_energy).state - id(cooling_energy_base);
double volume_consumed = id(m_volume).state - id(cooling_volume_base);
ESP_LOGI("custom", "Base MWh: %f Base m³: %f Consumed MWh: %f Consumed m³: %f Calc. cooling °C: %f", id(cooling_energy_base), id(cooling_volume_base), energy_consumed, volume_consumed, (energy_consumed * ${energy_uom}) / volume_consumed);
return (energy_consumed * ${energy_uom}) / volume_consumed;
# Calculated high resolution Energy sensor - delta base value added to the raw high resolution energy register
# This high resolution sensor will provide additional 2 decimals resolution -> 0.01 kWh
# If high resolution sensor for some reason can't be calculated (e.g. missing register data), it will instead return the low resolution energy register value
# The sensor is updated via call from the cooling sensor. Specific update sequence needed for cooling sensor to be correctly calculated.
- name: "Energy HiRes"
platform: template
id: m_energy_hires
update_interval: never
# If your meter model is Multical 602, then sensor will be exposed to HA
internal: ${model_602}
icon: "mdi:lightning-bolt"
unit_of_measurement: MWh
accuracy_decimals: 5
state_class: "total_increasing"
device_class: "energy"
lambda: |-
if (!${model_602}) {
if ((!isnan(id(m_energy_high).raw_state)) && id(delta_base) != -1) {
ESP_LOGI("custom", "delta_base OK. Return calculated energy HiRes value: %f MWh", (id(m_energy_high).state + id(delta_base)));
return (id(m_energy_high).state + id(delta_base));
} else {
id(temp) = roundf(id(m_energy).state * 1000);
id(energy_lowres_2digits) = id(temp) / 1000;
ESP_LOGW("custom", "delta_base NOT set! Return default LowRes energy register value: %f MWh", id(energy_lowres_2digits));
return id(energy_lowres_2digits);
}
} else {
ESP_LOGI("custom", "Not a Multical 602 meter - Skipping update");
return {};
}
# Multical Custom Sensors - Read directly from the Multical meter
#
# Multical 602 reports this register in UOM MWh -> Float with three significant decimals returned -> Resolution 1 kWh
# Multical 402 reports this register in UOM ?? Resolution ??
# Multical 403 reports this register in UOM kWh -> Integer value returned -> Resolution 1 kWh
- name: "Energy"
platform: template
id: m_energy
icon: "mdi:lightning-bolt"
unit_of_measurement: MWh
accuracy_decimals: 3
state_class: "total_increasing"
device_class: "energy"
# Multical 403 meter reports HiRes register in Wh
# Revised sensor to return value to HA in kWh with three decimals
- name: "Energy HiRes 403"
platform: template
id: m_energy_hires_403
# If your meter model is Multical 403, then sensor will be exposed to HA
internal: ${model_403}
icon: "mdi:lightning-bolt"
unit_of_measurement: kWh
accuracy_decimals: 3
state_class: "total_increasing"
device_class: "energy"
filters:
- multiply: 0.001
# Multical 602 meter - High resolution energy register in MWh with five significant decimals -> Resolution 0.01kWh
# Sensor is base for Multical Energy HiRes sensor calculation
# Sensor could be made internal, as it's not really needed in HA
# Register returns a total increasing value with an unknown offset
# Currently it's unknown when resister is reset and how to trigger this (no optical sensor on module for xx hours ?)
- name: "Energy HiRes (raw)"
platform: template
id: m_energy_high
# If your meter model is Multical 602, then sensor will be exposed to HA
internal: ${model_602}
icon: "mdi:lightning-bolt"
unit_of_measurement: MWh
accuracy_decimals: 5
state_class: "total_increasing"
device_class: "energy"
filters:
- multiply: 0.000001
- name: "Volume"
platform: template
id: m_volume
icon: "mdi:water-pump"
unit_of_measurement: m³
accuracy_decimals: 2
state_class: "measurement"
- name: "Temperature In"
platform: template
id: m_tin
icon: "mdi:thermometer"
unit_of_measurement: °C
accuracy_decimals: 2
state_class: "measurement"
device_class: "temperature"
- name: "Temperature Out"
platform: template
id: m_tout
icon: "mdi:thermometer"
unit_of_measurement: °C
accuracy_decimals: 2
state_class: "measurement"
device_class: "temperature"
- name: "Temperature Differential"
platform: template
id: m_tdiff
icon: "mdi:thermometer"
unit_of_measurement: °C
accuracy_decimals: 2
state_class: "measurement"
device_class: "temperature"
- name: "Power"
platform: template
id: m_power
icon: "mdi:flash"
unit_of_measurement: kW
accuracy_decimals: 1
state_class: "measurement"
device_class: "power"
- name: "Flow"
platform: template
id: m_flow
icon: "mdi:speedometer"
unit_of_measurement: l/h
accuracy_decimals: 0
state_class: "measurement"
text_sensor:
- name: "Multical Latest Status"
platform: template
id: m_status
- name: "Multical Info"
platform: template
id: m_info
update_interval: 60 sec