-
Notifications
You must be signed in to change notification settings - Fork 24
/
esphome-magnetometer.yaml
380 lines (355 loc) · 13 KB
/
esphome-magnetometer.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
---
# This config uses the QMC5883L, a triple-axis magnetometer, to read your water meter or gas meter.
substitutions:
# water or gas
device_class: 'water'
# mdi:water or mdi:meter-gas
device_icon: 'mdi:water'
# For water one of: CCF, ft³, gal, L, m³
# For gas one of: CCF, ft³, m³
volume_unit: 'gal'
i2c_scl: GPIO5 # D1
i2c_sda: GPIO4 # D2
# Set these only if you have connected two QMC5883L to one device, see esphome-two-meters.yaml
prefix_name: ''
prefix_id: ''
qmc5883l_update_interval: '5ms'
qmc5883l_oversampling: '64x'
volume_per_half_rotation_initial_value: '0.01008156'
flow_update_interval_seconds: '10'
hide_magnetic_field_strength_sensors: 'true'
hide_half_rotations_total_sensor: 'true'
esphome:
min_version: "2024.4.0" # This version includes https://github.com/esphome/esphome/pull/6456
# Default loop interval is 16ms. Setting it to 0 to make updates faster.
# Without setting this, the actual update interval of qmc5883l is: 6ms on ESP32, 10ms on ESP8266.
# With this, it's 5ms on ESP32, 8ms on ESP8266.
on_boot:
then:
- lambda: App.set_loop_interval(0);
logger:
level: INFO
logs:
qmc5883l: INFO
api:
services:
# Based on https://esphome.io/components/sensor/pulse_meter.html#re-setting-the-total-pulse-count
# Useful if you would like the total sensor to match what you see on your meter you are trying to match.
- service: ${prefix_id}set_total
variables:
new_total: float
then:
- globals.set:
id: ${prefix_id}half_rotations_total
value: !lambda 'return new_total / id(${prefix_id}volume_per_half_rotation).state;'
globals:
- id: ${prefix_id}half_rotations_total
type: long
restore_value: yes
initial_value: '0'
- id: ${prefix_id}half_rotations_flow
type: long
restore_value: no
initial_value: '0'
- id: ${prefix_id}axis_value_high
type: bool
restore_value: no
initial_value: 'false'
- id: ${prefix_id}calibrating
type: bool
restore_value: no
initial_value: 'false'
- id: ${prefix_id}calibrating_axis_x_min
type: float
restore_value: no
- id: ${prefix_id}calibrating_axis_x_max
type: float
restore_value: no
- id: ${prefix_id}calibrating_axis_y_min
type: float
restore_value: no
- id: ${prefix_id}calibrating_axis_y_max
type: float
restore_value: no
- id: ${prefix_id}calibrating_axis_z_min
type: float
restore_value: no
- id: ${prefix_id}calibrating_axis_z_max
type: float
restore_value: no
number:
- platform: template
id: ${prefix_id}volume_per_half_rotation
name: ${prefix_name}Volume per half rotation
entity_category: config
mode: box
min_value: 0
max_value: 9999999
step: 0.000000001
initial_value: ${volume_per_half_rotation_initial_value}
update_interval: never
restore_value: true
optimistic: true
unit_of_measurement: ${volume_unit}
- platform: template
id: ${prefix_id}temperature_offset
name: ${prefix_name}Temperature Offset
unit_of_measurement: '°C'
min_value: -100
max_value: 100
step: 0.1
mode: box
update_interval: never
optimistic: true
restore_value: true
initial_value: 33
icon: 'mdi:thermometer'
entity_category: config
- platform: template
id: ${prefix_id}threshold_lower
name: ${prefix_name}Threshold lower
entity_category: config
mode: box
min_value: -9999
max_value: 9999
step: 0.1
initial_value: -9999
update_interval: never
restore_value: true
optimistic: true
unit_of_measurement: µT
- platform: template
id: ${prefix_id}threshold_upper
name: ${prefix_name}Threshold upper
entity_category: config
mode: box
min_value: -9999
max_value: 9999
step: 0.1
initial_value: 9999
update_interval: never
restore_value: true
optimistic: true
unit_of_measurement: µT
- platform: template
id: ${prefix_id}calibration_seconds
name: ${prefix_name}Calibration time
entity_category: config
icon: mdi:timer
mode: box
min_value: 1
max_value: 999
step: 1
initial_value: 5
update_interval: never
restore_value: true
optimistic: true
unit_of_measurement: s
select:
- platform: template
id: ${prefix_id}axis
name: ${prefix_name}Axis
entity_category: config
options:
- x
- y
- z
- None
initial_option: None
update_interval: never
restore_value: true
optimistic: true
button:
- platform: template
id: ${prefix_id}calibrate_button
name: ${prefix_name}Calibrate axis
entity_category: config
on_press:
- lambda: |-
ESP_LOGI("calibration", "Starting calibration. Make sure ${device_class} is running");
id(${prefix_id}calibrating_axis_x_min) = std::numeric_limits<float>::max();
id(${prefix_id}calibrating_axis_x_max) = std::numeric_limits<float>::lowest();
id(${prefix_id}calibrating_axis_y_min) = std::numeric_limits<float>::max();
id(${prefix_id}calibrating_axis_y_max) = std::numeric_limits<float>::lowest();
id(${prefix_id}calibrating_axis_z_min) = std::numeric_limits<float>::max();
id(${prefix_id}calibrating_axis_z_max) = std::numeric_limits<float>::lowest();
id(${prefix_id}calibrating) = true;
- delay: !lambda return id(${prefix_id}calibration_seconds).state * 1000;
- lambda: |-
id(${prefix_id}calibrating) = false;
float x_range = id(${prefix_id}calibrating_axis_x_max) - id(${prefix_id}calibrating_axis_x_min);
float y_range = id(${prefix_id}calibrating_axis_y_max) - id(${prefix_id}calibrating_axis_y_min);
float z_range = id(${prefix_id}calibrating_axis_z_max) - id(${prefix_id}calibrating_axis_z_min);
if (x_range < 0) {
ESP_LOGW("${prefix_id}calibration", "Didn't get any values for x");
} else {
ESP_LOGI("${prefix_id}calibration", "x ranged from %.2f to %.2f",
id(${prefix_id}calibrating_axis_x_min), id(${prefix_id}calibrating_axis_x_max));
}
if (y_range < 0) {
ESP_LOGW("${prefix_id}calibration", "Didn't get any values for y");
} else {
ESP_LOGI("${prefix_id}calibration", "y ranged from %.2f to %.2f",
id(${prefix_id}calibrating_axis_y_min), id(${prefix_id}calibrating_axis_y_max));
}
if (z_range < 0) {
ESP_LOGW("${prefix_id}calibration", "Didn't get any values for z");
} else {
ESP_LOGI("${prefix_id}calibration", "z ranged from %.2f to %.2f",
id(${prefix_id}calibrating_axis_z_min), id(${prefix_id}calibrating_axis_z_max));
}
std::string axis_with_largest_range;
float min, max;
if (x_range > 20 && x_range > y_range && x_range > z_range) {
axis_with_largest_range = "x";
min = id(${prefix_id}calibrating_axis_x_min);
max = id(${prefix_id}calibrating_axis_x_max);
} else if (y_range > 20 && y_range > x_range && y_range > z_range) {
axis_with_largest_range = "y";
min = id(${prefix_id}calibrating_axis_y_min);
max = id(${prefix_id}calibrating_axis_y_max);
} else if (z_range > 20 && z_range > x_range && z_range > y_range) {
axis_with_largest_range = "z";
min = id(${prefix_id}calibrating_axis_z_min);
max = id(${prefix_id}calibrating_axis_z_max);
} else {
ESP_LOGE("${prefix_id}calibration", "Calibration failed. No axis had a range of at least 20");
return;
}
float lower_threshold = min + (max - min) / 2 - 5;
float upper_threshold = min + (max - min) / 2 + 5;
ESP_LOGI("${prefix_id}calibration", "Selected %s axis with lower threshold: %.2f and upper threshold: %.2f",
axis_with_largest_range.c_str(), lower_threshold, upper_threshold);
id(${prefix_id}axis).make_call().set_option(axis_with_largest_range).perform();
id(${prefix_id}threshold_lower).make_call().set_value(roundf(lower_threshold * 10) / 10).perform();
id(${prefix_id}threshold_upper).make_call().set_value(roundf(upper_threshold * 10) / 10).perform();
i2c:
- id: ${prefix_id}i2c_bus
scl: ${i2c_scl}
sda: ${i2c_sda}
frequency: 50kHz
scan: false
sensor:
# Holds the magnetic field strength value of x, y, or z depending on the axis template select.
# Increments counters when value crosses thresholds.
- platform: template
id: ${prefix_id}axis_value
internal: true
on_raw_value:
then:
- lambda: |-
if (x > id(${prefix_id}threshold_upper).state && !id(${prefix_id}axis_value_high)) {
id(${prefix_id}axis_value_high) = true;
id(${prefix_id}half_rotations_total) += 1;
id(${prefix_id}half_rotations_flow) += 1;
id(led).turn_on();
} else if (x < id(${prefix_id}threshold_lower).state && id(${prefix_id}axis_value_high)) {
id(${prefix_id}axis_value_high) = false;
id(led).turn_off();
}
filters:
- delta: 3
# https://esphome.io/components/sensor/qmc5883l.html
- platform: qmc5883l
id: ${prefix_id}qmc5883l_id
i2c_id: ${prefix_id}i2c_bus
address: 0x0D
field_strength_x:
id: ${prefix_id}qmc5883l_axis_x
name: ${prefix_name}Magnetic Field Strength X
internal: ${hide_magnetic_field_strength_sensors}
entity_category: diagnostic
on_raw_value:
then:
- lambda:
if (id(${prefix_id}axis).state == "x") id(${prefix_id}axis_value).publish_state(x);
if (id(${prefix_id}calibrating)) {
id(${prefix_id}calibrating_axis_x_min) = min(id(${prefix_id}calibrating_axis_x_min), x);
id(${prefix_id}calibrating_axis_x_max) = max(id(${prefix_id}calibrating_axis_x_max), x);
}
filters:
- delta: 3
field_strength_y:
id: ${prefix_id}qmc5883l_axis_y
name: ${prefix_name}Magnetic Field Strength Y
internal: ${hide_magnetic_field_strength_sensors}
entity_category: diagnostic
on_raw_value:
then:
- lambda: |-
if (id(${prefix_id}axis).state == "y") id(${prefix_id}axis_value).publish_state(x);
if (id(${prefix_id}calibrating)) {
id(${prefix_id}calibrating_axis_y_min) = min(id(${prefix_id}calibrating_axis_y_min), x);
id(${prefix_id}calibrating_axis_y_max) = max(id(${prefix_id}calibrating_axis_y_max), x);
}
filters:
- delta: 3
field_strength_z:
id: ${prefix_id}qmc5883l_axis_z
name: ${prefix_name}Magnetic Field Strength Z
internal: ${hide_magnetic_field_strength_sensors}
entity_category: diagnostic
on_raw_value:
then:
- lambda:
if (id(${prefix_id}axis).state == "z") id(${prefix_id}axis_value).publish_state(x);
if (id(${prefix_id}calibrating)) {
id(${prefix_id}calibrating_axis_z_min) = min(id(${prefix_id}calibrating_axis_z_min), x);
id(${prefix_id}calibrating_axis_z_max) = max(id(${prefix_id}calibrating_axis_z_max), x);
}
filters:
- delta: 3
temperature:
id: ${prefix_id}qmc5883l_temperature
name: ${prefix_name}Temperature
filters:
- lambda: "return x + id(${prefix_id}temperature_offset).state;"
- sliding_window_moving_average:
window_size: 3000
send_every: 3000
- or:
- throttle: 300s
- delta: 0.5
oversampling: ${qmc5883l_oversampling}
update_interval: ${qmc5883l_update_interval}
- platform: template
id: ${prefix_id}sensor_half_rotations_total
name: ${prefix_name}Half rotations total
lambda: return id(${prefix_id}half_rotations_total);
update_interval: 1s
internal: ${hide_half_rotations_total_sensor}
entity_category: diagnostic
accuracy_decimals: 0
state_class: 'total_increasing'
icon: 'mdi:counter'
filters:
- delta: 1
- platform: template
id: ${prefix_id}sensor_total
name: ${prefix_name}Total
lambda: return id(${prefix_id}half_rotations_total) * id(${prefix_id}volume_per_half_rotation).state;
update_interval: 10s
accuracy_decimals: 2
device_class: ${device_class}
icon: ${device_icon}
state_class: total_increasing
unit_of_measurement: ${volume_unit}
filters:
- or:
- throttle: 60s
- delta: 0.01
- platform: template
id: ${prefix_id}sensor_flow
name: ${prefix_name}Flow
lambda: |-
float flow = id(${prefix_id}half_rotations_flow) * id(${prefix_id}volume_per_half_rotation).state * 60.0 / ${flow_update_interval_seconds};
id(${prefix_id}half_rotations_flow) = 0;
return flow;
update_interval: ${flow_update_interval_seconds}s
accuracy_decimals: 2
state_class: measurement
unit_of_measurement: ${volume_unit}/min
filters:
- or:
- throttle: 60s
- delta: 0.01