Skip to content

Instantly share code, notes, and snippets.

@baudneo
Last active February 6, 2024 09:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save baudneo/f529bcffa0b876e0c61f5f7189b92af3 to your computer and use it in GitHub Desktop.
Save baudneo/f529bcffa0b876e0c61f5f7189b92af3 to your computer and use it in GitHub Desktop.
Esp8266 and qmc58xxx magnetometer - meter reader
esphome:
name: watermeter-magnet
friendly_name: watermeter
esp8266:
board: nodemcuv2
logger:
level: INFO
web_server:
port: 80
api:
encryption:
key: "xxxxxxxxxxxxxxxC3lOnN/+JAmQH7ls="
ota:
password: "xxxxxxcccc5bb3757ab72da5"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Watermeter-ESP8266"
password: "xxxxxxxxxx5flyu"
i2c:
sda: D2
scl: D1
scan: False
substitutions:
friendly_name: "watermeter"
switch:
- platform: restart
name: $friendly_name restart
binary_sensor:
- platform: status
name: "Status"
device_class: connectivity
icon: mdi:power
- name: "Magnet High"
platform: template
icon: mdi:magnet
lambda: |-
return id(water_magnet_high);
button:
- platform: template
name: "Reset pulse counter"
id: reset_pulse_button
on_press:
- lambda: id(magnet_rotations) = 0;
- platform: template
name: "Reset total consumption"
id: reset_consumption_button
on_press:
- lambda: id(estimated_consumption) = 0;
- platform: template
name: "Toggle Magnet High"
id: toggle_mag_high_button
on_press:
- lambda: |-
if (id(water_magnet_high) == true) {
id(water_magnet_high) = false;
} else if (id(water_magnet_high) == false) {
id(water_magnet_high) = true;
} else{
id (water_magnet_high) = false;
}
text_sensor:
- platform: wifi_info
ip_address:
name: "IP Address"
icon: mdi:ip
mac_address:
name: "MAC Address"
icon: mdi:network
- platform: version
name: "Version"
icon: mdi:cube-outline
interval:
# 1ms less than sensor read to make sure it always gets read
- interval: 9ms
then:
- lambda: |-
long ts = id(legit_ts);
long diff_ = 0;
long curr_ = millis();
if (id(mag_z).state >= 25 && !id(water_magnet_high)) {
if (ts > 0) {
diff_ = curr_ - id(legit_ts);
ESP_LOGI("", "Magnet: HIGH >>> LOW to HIGH duration: %d ms", diff_);
}
id(legit_ts) = curr_;
id(water_magnet_high) = true;
id(magnet_rotations) += .5;
id(rotation_counter) += .5;
id(rotation2L_counter) += .5;
} else if (id(mag_z).state <= -25) {
if (id(water_magnet_high)) {
id(water_magnet_high) = false;
if (ts > 0) {
diff_ = curr_ - id(legit_ts);
ESP_LOGI("", "Magnet: LOW >>> HIGH to LOW duration: %d ms", diff_);
}
id(legit_ts) = curr_;
id(magnet_rotations) += .5;
id(rotation_counter) += .5;
id(rotation2L_counter) += .5;
id(estimated_consumption) += id(ml_per_rotation) / 1000;
}
}
sensor:
- platform: uptime
name: $friendly_name Uptime
- platform: qmc5883l
address: 0x0D
field_strength_z:
name: "Field Z"
id: mag_z
internal: yes
range: 200uT
oversampling: 64
# lowest ive seena rotation take is 30-ish ms.
update_interval: 10ms
- name: "Flow L/min"
platform: template
id: flow_L_min
# C -= A is equivalent to C = C - A.
lambda: |-
int temp = id(rotation2L_counter);
id(rotation2L_counter) -= temp;
return (temp * id(ml_per_rotation)) / 1000;
update_interval: 60s
unit_of_measurement: "L/min"
- name: "Flow rotations/min"
platform: template
id: flow_rotate_min
lambda: |-
int temp = id(rotation_counter);
id(rotation_counter) -= temp;
return temp;
update_interval: 60s
unit_of_measurement: "rotations/min"
- name: "Estimated Consumed"
platform: template
id: total_consumed
lambda: |-
return id(estimated_consumption);
update_interval: 1s
unit_of_measurement: "L"
accuracy_decimals: 3
icon: mdi:water
device_class: water
state_class: total_increasing
# Update the estimated meter reading
on_value:
then:
- lambda: |-
if (id(estimated_consumption > 0)) {
id(meter_read_estimate) = (id(meter_read_start) + (id(estimated_consumption) / 1000)) + id(meter_offset);
} else {
id(meter_read_estimate) = id(meter_read_start) + id(meter_offset);
}
- name: "Magnet Rotations"
platform: template
id: show_magnet_rotations
lambda: |-
return id(magnet_rotations);
update_interval: 1s
unit_of_measurement: "rotation(s)"
state_class: total_increasing
- name: "Meter Should Read"
platform: template
id: meter_should_read
# meter base + consumed
state_class: measurement
update_interval: 1s
lambda: |-
return id(meter_read_estimate);
unit_of_measurement: 'm³'
accuracy_decimals: 3
device_class: water
icon: mdi:water
on_value:
then:
lambda: |-
id(meter_read_diff) = id(meter_read_actual) - id(meter_read_estimate);
- name: "Meter Read - Difference"
platform: template
id: meter_should_read_difference
update_interval: 1s
# meter actual - estimated
lambda: |-
return id(meter_read_diff);
state_class: measurement
unit_of_measurement: 'm³'
accuracy_decimals: 3
device_class: water
icon: mdi:water
number:
- platform: template
name: "Estimate Offset - Input"
id: meter_offset_input
step: 0.001
min_value: 0
max_value: 100000
set_action:
lambda: |-
id(meter_offset) = x;
id(meter_read_estimate) = id(meter_read_estimate) + x;
lambda: |-
return id(meter_offset);
unit_of_measurement: 'm³'
- platform: template
# This is what the meter reads when you start recording data to see if estimated = actual.
# Read your meter, input the number to this var and esphome will take care of the rest
name: "Base Meter Read - Input"
id: meter_base_input
step: 0.001
min_value: 0
max_value: 100000
set_action:
lambda: |-
id(meter_read_start) = x;
if (id(estimated_consumption > 0)) {
id(meter_read_estimate) = (id(meter_read_start) + (id(estimated_consumption) / 1000)) + id(meter_offset);
} else {
id(meter_read_estimate) = id(meter_read_start) + id(meter_offset);
}
lambda: |-
return id(meter_read_start);
unit_of_measurement: 'm³'
device_class: water
icon: mdi:water
# mL per full magnet rotation.
- name: "mL per pulse input"
platform: template
id: m_per_pulse_input
step: 0.01
min_value: 0
max_value: 10000
unit_of_measurement: 'mL'
device_class: water
icon: mdi:water-percent
set_action:
lambda: |-
id(ml_per_rotation) = x;
lambda: |-
return id(ml_per_rotation);
# Every once in awhile enter what the meter reads so esphome can report
# The difference between estimated vs actual. This helps when calibrating
# what 1 full magnet rotation consumes.
- platform: template
name: "meter actual input"
step: 0.001
min_value: 0
max_value: 100000
set_action:
lambda: |-
id(meter_read_actual) = x;
id(meter_read_diff) = id(meter_read_actual) - id(meter_read_estimate);
lambda: |-
return id(meter_read_actual);
unit_of_measurement: 'm³'
device_class: water
icon: mdi:water
# 75.7082
# Maximum liters per minute flow
- platform: template
name: "meter max flow input"
step: 0.001
min_value: 0
max_value: 1000000
set_action:
lambda: |-
id(meter_flow_max) = x;
lambda: |-
return id(meter_flow_max);
unit_of_measurement: 'L/min'
device_class: water
icon: mdi:water
globals:
- id: meter_flow_max
type: float
restore_value: yes
initial_value: "0.000"
- id: meter_read_actual
type: float
restore_value: yes
initial_value: '0.0000'
- id: meter_read_diff
type: float
restore_value: yes
initial_value: '0.0000'
- id: estimated_consumption
type: float
restore_value: yes
initial_value: '0.000'
- id: magnet_rotations
type: float
restore_value: yes
initial_value: '0'
# rotations to L per min
- id: rotation2L_counter
type: float
restore_value: no
initial_value: '0'
# rotations per min
- id: rotation_counter
type: float
restore_value: no
initial_value: '0'
- id: water_magnet_high
type: bool
restore_value: yes
initial_value: 'false'
- id: ml_per_rotation
type: float
restore_value: yes
initial_value: "65.71"
- id: meter_read_start
type: float
restore_value: yes
initial_value: "0.0000"
- id: legit_ts
type: long
restore_value: no
initial_value: "0"
- id: meter_read_estimate
type: float
restore_value: no
initial_value: '0.0000'
- id: rotation_time_ms
type: long
restore_value: yes
initial_value: "0"
- id: meter_offset
type: float
restore_value: yes
initial_value: "0"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment