I spent a lot (A LOT!) of time trying to figure out exactly what you are trying to do. It didn’t help that I wasn’t quite sure what I wanted as I iterated. I have it working in a satisfactory way now.
The tricky part, as you probably already know, is that you have to use a retained MQTT message so that your device will still receive it when it wakes up. But, if the message is retained, it will still be there forever, even after the reboot after your OTA update.
My solution was to give up trying to do it with mqtt:
on_message:
(which has the convenience of checking the payload for you.) Instead, I use an mqtt_subscribe
text sensor. That preserves the payload in the sensor’s state
variable. When it sees the “stay awake” message, it sends a new retained message to that topic to clear out the “stay awake” message. It’s a bit inconvenient because of the cumbersome YAML nesting of if/then/else, but here’s what it looks like:
text_sensor:
- platform: mqtt_subscribe
name: "deep sleep control"
id: m_deepsleep
topic: ${node_name}/deepsleep
internal: true
qos: 2
on_value:
then:
- if:
condition:
text_sensor.state:
id: m_deepsleep
state: "prevent ${mqtt_secretword}"
then:
- logger.log: "deepsleep prevent"
- globals.set:
id: can_sleep
value: 'false'
- mqtt.publish:
topic: ${node_name}/deepsleep
payload: 'ignore me'
retain: true
- mqtt.publish:
topic: ${node_name}/status
payload: '${node_name} ===================='
- mqtt.publish:
topic: ${node_name}/status
payload: '${node_name} AWAKE! AWAKE! AWAKE!'
- mqtt.publish:
topic: ${node_name}/status
payload: '${node_name} ===================='
else:
- if:
condition:
text_sensor.state:
id: m_deepsleep
state: "enter ${mqtt_secretword}"
then:
- logger.log: "deepsleep enter"
- delay: 1s
- globals.set:
id: can_sleep
value: 'true'
else:
- if:
condition:
text_sensor.state:
id: m_deepsleep
state: 'ignore me'
then:
else:
- logger.log:
level: WARN
format: 'BOGUS ${node_name}/deepsleep message: %s'
args: [ id(m_deepsleep).state.c_str() ]
Since my deep sleep is 20 minutes long for this device, I have a distinctive message emitted in the MQTT status stream for the device to try to get me to notice when it finally wakes up. Otherwise, it will just stay awake and use up the battery that I’m trying to preserve with the deep sleep in the first place.
I’ve been using mosquitto_pub
to send the messages and mostquitto_sub
to monitor things. I send the “stay awake” message as retained, but the “go to sleep” message is non-retained. That’s something of a holdover from when the config was organized a bit differently, and receiving the “go to sleep” message would trigger an immediate deep sleep. A retained message would make that happen every time the device woke up, before it had a chance to do real work.
I’m not making the prevent
and enter
calls directly in that text sensor. Instead, I’m using a global boolean variable. The reason for that is because I use an esphome:
on_boot
trigger to read my environmental sensor (a BME280 with an update_interval: never
) as soon as possible and then go back to sleep. The code won’t go back to sleep if the global boolean tells it not to. That looks like this:
on_boot:
then:
- deep_sleep.prevent
- while:
condition:
not:
mqtt.connected:
then:
- delay: 0.2s
- logger.log:
level: INFO
format: "MQTT is connected"
- component.update: i_bme280
- while:
condition:
lambda: 'return !id(can_sleep);'
then:
- delay: 1s
- mqtt.publish:
topic: ${node_name}/status
payload: '${node_name} about to go to sleep for ${deepsleep_sleep}'
- delay: 1s
- deep_sleep.enter
There is probably a less convoluted way to go about this, but I think I spent enough time fiddling with it already. Maybe someday I’ll go back and squeeze out some of those delays.