Hello everyone, I’ll try to summarize here what I’ve learned:
First, context: I was building a mailbox notifier, that was just on a border of wifi availability. In addition to that, I made a power latch / cutoff circuit specifically tailored to the task, resulting in 0.0µA - that is, as far as my DMM can measure it. I tried to connect a li-poly battery to an onboard linear regulator, however, it seems that the voltage drop sets in rather quickly and is quite big. I’ll be moving over to lifepo4 cell, once they arrive, as I can bypass the regulator with their voltage range.
As far as the connectivity goes, I chose to go with MQTT. Regarding wifi, just a small note, in case you made the lowest allowed rate of the 802.11/b clients higher, you might experience connectivity issues - like I did.
About the core of the issue - I’m not happy with my solution as it isn’t much better than other solutions, already mentioned here, however I have settled with it for now.
Here’s the stripped-down yaml - hope I won’t remove anything essential:
esphome:
#...
globals:
- id: mailbox_occupancy_verified
type: bool
restore_value: no
initial_value: 'false'
- id: batt_voltage_verified
type: bool
restore_value: no
initial_value: 'false'
script:
- id: invert_led
then:
- switch.toggle: led_builtin
- id: blink
then:
- switch.toggle: led_builtin
- delay: 100ms
- switch.toggle: led_builtin
- delay: 100ms
- id: check_and_poweroff
then:
- repeat:
count: 10 #read the adc enough times to fill the filter window
then:
- component.update: batt_adc
- delay: 10ms
- component.update: wifi_signal_db
- wait_until:
timeout: 10s
condition:
- lambda:
return id(mailbox_occupancy_verified);
- deep_sleep.prevent: deepsleep
- wait_until: # if the mailbox doors are left open, it also serves as an OTA mode indicator - don't sleep
condition:
binary_sensor.is_off: access_doors
- component.update: upT
- delay: 100ms #hopefully uptime will make it in time, as the more stuff we do after now the less accurate the uptime metric will be.
- deep_sleep.allow: deepsleep
- deep_sleep.enter: deepsleep
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: True
manual_ip:
# ...
power_save_mode: HIGH
# power_save_mode: LIGHT
# power_save_mode: NONE
on_connect:
then:
- script.execute: blink #nothing like a good old led debugging...
mqtt:
# id: mqtt
broker: 192.168.0.60
username: !secret mqtt_uname_iot
password: !secret mqtt_passwd_iot
birth_message:
will_message:
on_connect:
then:
- script.execute: blink
- script.execute: check_and_poweroff
deep_sleep:
run_duration: 30sec #should kick in in case wifi doesn't connect in 30 sec but for some reason it doesn't...
id: deepsleep
sensor:
- platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: never
entity_category: "diagnostic"
- platform: copy # Reports the WiFi signal strength in %
source_id: wifi_signal_db
id: wifi_signal_percent
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "Signal %"
entity_category: "diagnostic"
- platform: uptime
name: "Uptime"
id: upT
update_interval: never
unit_of_measurement: s
accuracy_decimals: 3
- platform: adc
pin: 3
name: "Battery ADC"
id: batt_adc
update_interval: never
attenuation: 11db
accuracy_decimals: 2
filters:
- multiply: 2
- offset: +0.05
- platform: copy
source_id: batt_adc
id: batt_voltage_filtered
icon: "mdi:battery"
name: Battery Voltage
unit_of_measurement: V
accuracy_decimals: 2
filters:
- max:
window_size: 10
send_every: 10
send_first_at: 10
- platform: copy
source_id: batt_voltage_filtered
id: batt_level
icon: "mdi:battery"
name: Battery Percent
unit_of_measurement: '%'
accuracy_decimals: 0
filters:
# Map from voltage to Battery level
- calibrate_linear:
- 3.6 -> 0
- 4.1 -> 100
#Overide values less than 0% and more than 100%
- lambda: |
if (x < 0) return 0;
else if (x > 100) return 100;
else return ceil(x / 5) * 5;
- platform: internal_temperature
name: "Internal Temperature Sensor"
switch:
- platform: shutdown
name: "ESP deepsleep"
- platform: gpio
id: power_cutoff #pin to cut power to self, currently not in use since entering deep sleep has the equivalent effect, and that way esphome expects power loss.
pin:
number: 12
mode:
output: True
name: "Power cutoff"
setup_priority: 800
restore_mode: ALWAYS_ON
- platform: gpio
pin:
number: 15
mode:
output: True
name: "onboard LED"
id: led_builtin
setup_priority: 800
restore_mode: ALWAYS_OFF
binary_sensor:
- platform: gpio
pin:
number: 7
inverted: False
mode:
input: True
pulldown: True
publish_initial_state: True
id: access_doors
name: "ACCESS DOORS"
device_class: door
setup_priority: 800
- platform: gpio
pin:
number: 11
inverted: False
mode:
input: True
pulldown: True
name: "Occupancy"
id: mailbox_occupancy
device_class: occupancy
setup_priority: 800
publish_initial_state: True
filters:
- lambda: !lambda |-
// Woke up. Modify my reported state depending on whether the mailbox doors are open.
if (id(access_doors).state) {
return 0; //The mailbox access doors are open. Therefore the mailbox will now be empty.
} else {
return 1; //Woke up but the mailbox doors aren't open. Therefore input door sensor must have been triggered. Something is now likely in the mailbox.
}
text_sensor:
- platform: mqtt_subscribe
id: verify_mailbox
name: "Occupancy verify"
setup_priority: -100
topic: mailbox-32-s2/binary_sensor/occupancy/state
filters:
- lambda: |-
if (x == "OFF" && id(mailbox_occupancy).state == 0) {
id(mailbox_occupancy_verified) = true;
return (std::string) "VERIFIED"; }
if (x == "ON" && id(mailbox_occupancy).state == 1) {
id(mailbox_occupancy_verified) = true;
return (std::string) "VERIFIED"; }
id(mailbox_occupancy_verified) = false;
return (std::string) "NOT_VERIFIED";
# - platform: mqtt_subscribe
# name: "Battery voltage verify"
# id: verify_batt_level
# setup_priority: -100
# topic: 32-s2-wemoss2-mini/sensor/battery_voltage/state
# filters:
# - lambda: |-
# std::string s = to_string(round(id(batt_voltage_filtered).state * 1000) / 1000);
# s = s.substr(0, s.find(".")+3);
# if (x == s) {
# //if (strtof(x.c_str(), NULL) == id(batt_voltage_filtered).state) {
# //if (x == to_string(round(id(batt_voltage_filtered).state * 100) / 100)) {
# id(batt_voltage_verified) = true;
# return (std::string) "VERIFIED"; }
# //return to_string(strtof(x.c_str(), NULL));
#
# id(batt_voltage_verified) = false;
# //return (std::string) "NOT_VERIFIED";
# //return to_string(id(batt_voltage_filtered).state);
# return s;
#
An explanation is in order now:
By triggering either one of the physical switches on the mailbox doors, the ESP will be supplied power. ESP then asap set the power cutoff pin to high which will ensure the power will remain turned on.
All relevant input states should also be acquired right now due to a combination of setup_priority
and publish_initial_state
.
After the wifi connects, the MQTT server connection is right around the corner, and the relevant script is triggered. I believe the script itself is sufficiently clear, so I won’t go into any details unless requested.
The binary sensor is the most important to get its reading across. Due to the usage of MQTT this is achieved by simply subscribing to the same topic, where button state is published. This additionally gives the “VERIFIED” / “NOT_VERIFIED” status in home assistant as a bonus.
It doesn’t really matter if the correct reading is already in the MQTT since sending it again, would change nothing.
So far it works well, but has some serious limitations. For example. You can find remains of my attempts to do the same with the battery voltage. Here we have some issues with floating point numbers since the sensor sends out rounded values but .state
returns “raw” value. Now this might be just a floats limitation, but it’s still a headache. Maybe I can copy the sensor…
The other limitation - besides obvious small but unnecessary traffic, the MQTT subscribe path has to be updated manually on hostname changes or the sensor changes, and I haven’t found any way to get it from some variables.
Overall it’s … disappointing, but perchance it’ll be more useful for someone else.
Well then, I think that’s about it~!