This module provides publish and subscribe MQTT access using ESPNow. Benefits
relative to running an mqtt_as
client are:
- Nodes can be designed to have extremely low power consumption.
- Node scripts can comprise a small amount of synchronous code. This is of particular benefit on ESP8266.
- WiFi and broker outages are handled by the gateway rather than by a large library running on the node.
The gateway is an mqtt_as
client running on ESP32 hardware. A good WiFi
connection provides access to an MQTT broker. One or more nodes running on ESP
hardware use ESPNow to publish and to receive MQTT messages targeted on them.
Nodes may run synchronous or asynchronous code. They can run continuously or
operate in micropower mode where they spend most of the time in deepsleep.
ESPNow enables substantial power saving compared to a normal mqtt_as
client.
While an mqtt_as
client can be put into deepsleep between publications,
re-establishing a WiFi connection and re-connecting to the broker takes time
and consumes power. By contrast ESPNow can start very quickly after a wake
from deepsleep and communications can complete quickly.
Continuously running asynchronous nodes can receive subscription messages with low latency. Clearly this is not the case for micropower nodes which can only receive messages when they wake: the gateway queues messages for when the node wakes.
There are tradeoffs relative to running an mqtt_as
client. The ESPNow node
supports only basic publish and subscribe MQTT operations, whereas the
mqtt_as
client adds broker directives such as last will and clean session.
The qos==1
guarantee is honoured for publications from the node. Messages to
the node are normally assured of delivery, however while the node is asleep the
gateway buffers messages in a fixed-size queue. If the node does not wake in
time to receive them, the messages will be lost, starting with the oldest.
Handling of broker outages and WiFi channel changes is automatic in an
mqtt_as
node, and can be automatic in synchronous nodes. Micropower nodes can
be automatic, but extra power saving can be achieved with application support.
On the plus side code is small and can be run on an ESP8266 without special precautions with over 25K of free RAM. Achieving long term battery opertaion is easy.
The gateway requires an ESP32, preferably a standard ESP32 with or without SPIRAM. Nodes may be ESP32, ESP8266, or ESP32 variants. Some ESP32-S3 boards have had issues: see Appendix 2.
Messages between the gateway and the broker conform to MQTT V3.1.1.
A micropower publish-only application can be simple. The following publishes a reading once per minute from the ambient light sensor of a FeatherS3 board:
from machine import deepsleep, ADC, Pin
import time
from .link import Link
gateway = "2462abe6b0b5" # Identity of gateway
# Case where WiFi AP channel is known
channel = 3 # WiFi AP channel
credentials = None # Fixed channel
gwlink = Link(gateway, channel, credentials)
adc = ADC(Pin(4), atten = ADC.ATTN_11DB)
msg = str(adc.read_u16())
gwlink.publish("light", msg, False, 0)
gwlink.close()
deepsleep(60_000) # main.py runs the app again when deepsleep ends.
In this example the AP is assumed to run on a fixed channel (3). The gateway forwards the publication to the broker which may be local or on the internet.
The following script wakes every 10s, receives any messages published to "foo_topic" or "allnodes" and re-publishes them to "shed".
from machine import deepsleep, Pin
from .link import Link
gateway = "2462abe6b0b5" # Identity of gateway
# Case where WiFi AP channel is not known and may vary
channel = None # Indicate unknown channel
credentials = ("ssid", "password") # WiFi access
gwlink = Link(gateway, channel, credentials)
def echo(topic, message, retained):
gwlink.publish("shed", message)
gwlink.subscribe("foo_topic", 1)
gwlink.get(echo) # Get any pending messages
gwlink.close()
deepsleep(10_000)
# Now effectively does a hard reset: main.py restarts the application.
The gateway queues subscriptions, forwarding them to the node when it wakes. The node receives a JSON encoded 3-list comprising topic name, payload and the retain flag.
In this example the AP may change channel. Connection to WiFi is used to associate the node with the current channel. An alternative way to determine the channel exists: this avoids having to use WiFi credentials and saves power at the cost of a small amount of additional application code.
This requires the following steps. Ensure that all ESP devices to be used have the latest daily build of firmware.
The ESPNow protocol requires that the WiFi channel does not change. Some access
points or routers pick a channel on power up or may change channel in response
to varying radio conditions. Ideally the AP should be set to use a fixed
channel. This enables a rapid recovery from an outage and minimises power
consumption of micropower nodes. If a fixed channel is impractical the Link
class provides means of adapting to variation.
On the gateway ESP32 device, connect to WiFi and install with
import mip
mip.install("github:peterhinch/micropython-mqtt/gateway")
Alternatively, with no need to connect the target to WiFi:
$ mpremote mip install github:peterhinch/micropython-mqtt/gateway
Edit the file lib/gateway/mqtt_local.py
on the device to include the correct
WiFi credentials and broker IP address. This file is as follows:
from .mqtt_as import config
# Entries must be edited for local conditions
config["server"] = "192.168.0.10" # Broker
# config["server"] = "test.mosquitto.org"
config["ssid"] = "your_network_name"
config["wifi_pw"] = "your_password"
The following tests verify communication between the gateway and the broker. Assuming that the broker is on 192.168.0.10, open a terminal and issue
$ mosquitto_sub -h 192.168.0.10 -t gw_status
Start the gateway by issuing the import at the device REPL - output similar to the following should ensue:
>>> import gateway
ESPNow ID: b"70041dad8f15"
Checking WiFi integrity.
Got reliable connection
Connecting to broker.
Connected to broker.
Gateway b'70041dad8f15' connected to broker 192.168.0.10.
The terminal running mosquitto_sub
should show
12/7/2023 16:52:13 Gateway b'70041dad8f15' connected to broker 192.168.0.10.
Keep a record of the gateway ID (b'70041dad8f15'
in the above example). This
is its MAC address in hex format and is required by the nodes.
On the node device, connect to WiFi and install with
import mip
mip.install("github:peterhinch/micropython-mqtt/gateway/nodes")
Or, on the PC run
$ mpremote mip install github:peterhinch/micropython-mqtt/gateway/nodes
Configuration may be done by editing the file lib/nodes/link_setup.py
. This
creates the following variables which are the constructor arguments for the
Link
class. Using a file simplifies distributing common args to multiple
nodes:
gateway
MAC address of the gateway as a 12 character string.debug
True
to output debug messages.channel
Set to channel number if fixed, elseNone
.credentials
Set toNone
if channel is fixed else('ssid', 'password')
.
See # 5. The Link class for full details.
The following demos will be installed on the node:
synctx.py
General demo of publication and subscription.pubonly.py
Micropower publish-only demo.pubonly_gen.py
Micropower publish-only demo. Shows an efficient means of handling channel changes.slow_echo.py
Micropower demo which wakes periodically, re-publishing any messages occurring during its sleep.
With the gateway running issue import nodes.synctx
. The following is typical
output, with the response to two publications from mosquitto_pub
:
>>> import nodes.synctx
Link init.
reconnect chan 3 creds None
connected on channel 3
Actual channel 3
Got subscription topic: "allnodes" message: "hello there pete" retained False
Got subscription topic: "foo_topic" message: "hello" retained False
It should report publications at three second intervals. On a PC run the following (changing the IP address to that of the broker). Note the expected output:
$ mosquitto_sub -h 192.168.0.10 -t shed
Count 0 ESPNow fails 0 Broker fails 0 mem_free 8182272
Count 1 ESPNow fails 0 Broker fails 0 mem_free 8182160
Count 2 ESPNow fails 0 Broker fails 0 mem_free 8182160
Count 3 ESPNow fails 0 Broker fails 0 mem_free 8182160
Count 4 ESPNow fails 0 Broker fails 0 mem_free 8182160
Count 5 ESPNow fails 0 Broker fails 0 mem_free 8182160
To publish to the device run this, changing the IP address to match the broker:
$ mosquitto_pub -h 192.168.0.10 -t allnodes -m "hello" -q 1
The allnodes
topic publishes to all connected nodes.
Asynchronous applications are assumed to be continuously running: there is no micropower support. On the node device, connect to WiFi and install with:
import mip
mip.install("github:peterhinch/micropython-mqtt/gateway/anodes")
Or, on the PC run
$ mpremote mip install github:peterhinch/micropython-mqtt/gateway/anodes
Node configuration is done by editing the file lib/anodes/link_setup.py
. This
is as per synchronous mode, but adds one variable:
poll_interval = 1000
The asynchronous link polls the gateway periodically, prompting it to send any pending messages. This defines the interval (in ms) and determines the latency of incoming messages.
The following demo will be installed on the node:
asynctx.py
General demo of publication and subscription. Assumes a FeatherS3 board.
With the gateway running issue import anodes.asynctx
. The following is typical
output, with the response to two publications using mosquitto_pub (see below):
>>> import anodes.asynctx
Link init.
reconnect chan 3 creds None
connected on channel 3
Waiting for down
Got subscription topic: "foo_topic" message: "hello" retained False
Got subscription topic: "allnodes" message: "hello there pete" retained False
It should report publications at three second intervals. On a PC run the following (changing the IP address to that of the broker). Note the expected output:
$ mosquitto_sub -h 192.168.0.10 -t shed
Count 0 Response fails 0 mem_free 8176656
Count 1 Response fails 0 mem_free 8175200
Count 2 Response fails 0 mem_free 8175088
Count 3 Response fails 0 mem_free 8175088
Count 4 Response fails 0 mem_free 8175168
To publish to the device run this, changing the IP address:
$ mosquitto_pub -h 192.168.0.10 -t allnodes -m "hello" -q 1
This supports synchronous code including micropower applications.
Constructor args. These are normally defined in link_setup.py
to enable the
setup of mutiple nodes with common values.
gateway:str
12 character gateway ID e.g. "2462abe6b0b5".channel
Channel no. if known, elseNone
credentials
('ssid', 'password')
elseNone
. See section 5.1.debug=True
The constructor raises an OSError
if credentials are passed and it fails to
connect to WiFi.
Public methods:
publish(topic:str, msg:str, retain:bool=False, qos:int=0)
See below for return values.subscribe(topic:str, qos:int)
ReturnsTrue
unless ESPNow cannot connect to the gateway.get(callback)
Receive any pending messages. The callback will be run for each message. It takes argstopic:str, message:str, retained:bool
. Theget
method returnsTrue
on success,False
on communications failure.ping()
Check the gateway status. Return values are as perpublish
- see below.close()
This should be run prior todeepsleep
or application quit.breakout(Pin)
This is a convenience function for micropower applications. It can be hard to get back to a REPL whenmain.py
immediately restarts an application. Initialisingbreakout
with aPin
instance defined withPin.PULL_UP
allows the REPL to be regained: the pin should be linked to gnd and the node should be reset.get_channel()
Query the gateway's current channel. Returns anint
orNone
on fail.reconnect()
Force a reconnection. Returns the channel number. It is not normally needed: micropower applications instantiate aLink
each time they run while continuously running applications handle outages and channel changes automatically.
Public bound variable:
txpower=None
. Certain ESP32-S3 boards are unreliable when running at low channel numbers. The default sets transmit power to maximum: on these boards a value of 17 improves reliability. The value should be set before instantiating. See appendix 2.
The method of connection and of responding to AP channel changes depends on the
channel
and credentials
constructor args.
- Channel is fixed. This is the simplest case with lowest power consumption.
It is addressed by setting the
channel
arg and settingcredentials=None
. - Acquire the channel using WiFi, achieved by setting
channel=None
and providing SSID and password incredentials
. Channel changes are tracked automatically. Some duplicate publications may occur while the channel change is being detected. Initial connection on power up is slow (a few seconds); in micropower applications this equates to energy used. - Acquire the channel by querying the gateway, achieved with
channel=None
andcredentials=None
. In a variable channel environment this offers lower power consumption. To minimise power the application can be designed to manage connection. Thepubonly_gen.py
demo illustrates this. The channel is stored in nonvolatile RAM along with an error count. Normally theLink
is instantiated with the stored channel, which is fast. If consecutive errors exceed a threshold, theLink
is instantiated withchannel=None
prompting a gateway query.
The publish
method returns the following values, defined as constants in
link.py
.
PUB_OK
Success.BROKER_OUT
Broker is down. Messages have not been lost but gateway queue is half full. The application should delay sending more until aping
has succeeded.ESP_FAIL
ESPNow communications are down. Message was not sent.PUB_FAIL
Gateway queue is full, message was lost (publish only).
This supports asynchronous applications, which are assumed to be continuously running.
Constructor args. These are normally defined in link_setup.py
to enable the
setup of mutiple nodes with common values.
gateway
12 character string representing gateway ID e.g. '2462abe6b0b5'.channel
Channel no. if known, elseNone
credentials
('ssid', 'password')
ifchannel == None
elseNone
debug=True
poll_interval
Determines the period in ms between gateway polling messages. A short interval reduces incoming message latency at the cost of increasing the load on the gateway.
Public asynchronous methods:
run()
This should be run on application start. RaisesOSError
if credentials are passed and it cannot connect to WiFi.publish(topic:str, msg:str, retain:bool=False, qos:int=0)
This will block in the absence of ESPNow and broker connectivity.subscribe(topic:str, qos:int)
reconnect()
Forces a reconnection. Should not be required.
Public synchronous method:
close()
This should be run prior to application quit.
Public Event
instances:
broker_up
Set when gateway has connected to the broker.broker_down
Set when gateway has lost connectivity with broker.esp_up
Set when an ESPNow message is successfully sent to the gateway.esp_down
Set when an ESPNow message has failed.
An application using any of these events should clear them.
Public bound variable:
txpower = None
. Certain ESP32-S3 boards are unreliable when running at low channel numbers. The default sets transmit power to maximum: on these boards a value of 17 improves reliability. The value should be set before instantiating. See appendix 2.
Message retrieval:
An ALink
instance is an asynchronous iterator. Messages are retrieved with
async for
as per this example:
async def do_subs(lk): # lk is an ALink instance
await lk.subscribe("foo_topic", 1)
async for topic, message, retained in lk:
print(f'Got subscription topic: "{topic}" message: "{message}" retained {retained}')
There is a topic "allnodes"
: if an external device publishes to this topic,
the gateway will forward it to all nodes. The name of this topic may be changed
in the gateway configuration file.
The following is reference information. The writer of node applications may only need to be familiar with the gateway configuration. Subsequent paras describe the internal operation of the gateway.
The file gwconfig.py
instantiates a defaultdict
with entries defining the
mode of operation of the gateway. These have default values so the file may not
need to be edited for initial testing. Keys and defaults are as follows:
PubIn = namedtuple("PubIn", "topic qos") # Publication to gateway/nodes from outside
PubOut = namedtuple("PubOut", "topic retain qos") # Publication by gateway
gwcfg = defaultdict(lambda : None)
gwcfg["debug"] = True # Print debug info. Also causes more status messages to be published.
gwcfg["qlen"] = 10 # No. of messages to queue (for each node).
# If queue overruns (e.g. because node is asleep), oldest messages will be lost.
gwcfg["lpmode"] = True # Set True if all nodes are micropower: messages are queued
# and only forwarded to the node after it has published.
# If False the gateway will attempt to send the message on receipt, only queuing
# it on failure.
gwcfg["use_ap_if"] = True # Enable ESP8266 nodes by using AP interface. This has
# the drawback of advertising an AP. If all nodes are ESP32 this may be set False
# enebling station mode to be used. Note that this affects the gateway ID.
gwcfg["pub_all"] = PubIn("allnodes", 1) # Publish to all nodes with qos==1
# Optional keys
gwcfg["errors"] = PubOut("gw_errors", False, 1) # Gateway publishes any errors.
gwcfg["status"] = PubOut("gw_status", False, 0) # Destination for status reports.
gwcfg["statreq"] = PubIn("gw_query", 0) # Status request (not yet implemented)
# gwcfg["ntp_host"] = "192.168.0.10" # Override internet timeserver with local
gwcfg["ntp_offset"] = 1 # Local time = utc + offset
PubIn
objects refer to topics to which the gateway (rather than a node) will
respond. PubOut
topics are those to which the gateway may publish. If an
error occurs the gateway will publish it to the topic with key "errors"
. If
"debug" is True
the gateway will also print it.
Gateway publications may be prevented by omitting the relevant PubOut
key.
Gateway error and status reports have a timestamp. The module will attempt to
set the ESP32 RTC from an NTP timeserver. If the NTP daemon is run on a local
host the host's IP may be specified. Alternatively time setting may be disabled
by setting gwcfg["ntp_host"] = False
.
The gateway has a bound mqtt_as
client instance (.client
) and a dictionary
of nodes (.queues
). The dict keys are gateway MAC addresses. Each node has
a queue for subscription messages from the client. In general messages cannot
be delivered immediately because the node my be asleep. When a node first sends
a message to the gateway a new queues
key is created and a queue assigned.
Messages from node to gateway are JSON-encoded lists whose length defines the type of message. A single element list is a command, two elements denotes a subscription and four a publication. Other lengths are ignored and an error message published to the gateway error topic.
In the event of a broker outage, the client's publish method will block. The
gateway sends an "ACK"
message to the node when publication is complete. This
enables the link to provide feedback to the application preventing message
loss.
If the link requests messages or queries status (via .ping
), the gateway
responds with "UP"
or "DOWN"
.
Each node sends the gateway a message comprising a json-encoded 4-list. Elements are:
topic:str
message:str
retain:bool
qos:int
These are as per mqtt_as
documentation with the exception that bit 2 of qos
may be set: in this case the gateway will respond with an acknowledge. This
enables the design of nodes which re-transmit lost ESPNow messages. The
acknowledge takes the form of a subscribed message having topic and message
fields being "ACK"
(see below).
A device sends a message to a node by publishing a specially formatted message.
The topic must be one to which the gateway has subscribed. The gateway
subscribes to a default topic defined as a tuple in the file mqtt_local.py
. A
topic is defined as a 2-tuple with the following elements:
topic_name:str
qos:int
This is the qos to use in communication with the broker. The default is the topic "gateway" with qos of 1. It is defined as follows inmqtt_local.py
:
config["gwtopic"] = ("gateway", 1)
A typical publication looks like
$ mosquitto_pub -h 192.168.0.10 -t allnodes -m "hello" -q 1
Where 192.168.0.10
is the IP address of the broker, allnodes
is the gateway
topic, and "hello"
is the MQTT message. The message can optionally be a JSON
encoded object.
All nodes are set up to receive ESPNow messages via link_setup.py
. When a
device publishes to a topic to which a node is subscribed, the gateway forwards
an ESPNow message to the node. All nodes are subscribed to the default topic.
A normal message is a JSON-encoded 3-list comprising:
topic:str
message:str
This may itself be JSON-encoded for complex objects.retained:bool
"OUT"
and"ACK"
messages are 2-lists lacking theretained
element.
A node tests status by sending a ping
message or attempting to retrieve
messages from the gateway, which responds by sending a special "UP"
or
"DOWN"
message to the node depending on the state of the mqtt_as
client.
If publication is attempted during an outage the gateway responds with "NAK"
.
The gateway will continue trying until the outage ends, when it sends "ACK"
.
In the event of a "NAK"
the behaviour of the link depends on whether the
synchronous or asynchronous version is running. The asynchronous publish
pauses until publication is complete. The synchronous version returns False
.
The synchronous application can keep trying to send the next message until a
True
value occurs.
The gateway will publish status messages indicating connection state.
This is done by publishing to the default topic as defined in gwconfig.py
.
The message will be forwarded to all nodes which have communicated with the
gateway. With the default topic "allnodes":
$ mosquitto_pub -h 192.168.0.10 -t allnodes -m "hello" -q 1
When a node subscribes to a topic, the gateway responds as follows. If another
node has already subscribed to this topic, the node will be added to the set of
subscribed nodes. If no other node has subscribed to this topic, the gateway
will cause the mqtt_as
client to subscribe to the broker and will create a
record identifying the topic and the node. This ensures that future messages
are routed to that node.
Radio communications suffer from three potential issues:
- Corrupted messages.
- Duplicate messages.
- Missing messages.
In ESPNow Corrupted messages never seem to occur. The TCP/IP protocol ensures
they are not a problem for mqtt_as
.
Dupes can occur for various reasons, including the limitations of MQTT qos==1. ESPNow transmission uses a handshake to verify successful transmission. This can be pessimistic, reporting failure when success has occurred. In this situation the gateway will re-transmit, causing a dupe. There is also the issue of broadcast messages to clients that are awake, as discussed above. If dupes are an issue a message ID should be used to enable the application to discard them.
At MQTT level missing messages should never occur if qos==1 is used. ESPNow has a handshake which, in conjunction with the gateway design, should avoid missing messages from the node. For cast-iron certainty, node publications can request an acknowledge enabling the application to retransmit. Incoming node messages have no such mechanism and rely on the ESPNow handshake. I haven't yet established if the handshake can be considered "perfect" in preventing missed messages.
In general a node is designed to start, publish, optionally handle subscription
messages, deepsleep for a period, and quit. The main.py
script restarts it;
consequently no state is retained between runs. To minimise power consumption
code should run as quickly as possible: if possible looping constructs should
be avoided. Code is typically synchronous: uasyncio
has no support for sleep.
Messages received by the gateway and targeted on a node are queued. They are
forwarded when the node wakes up and issues gwlink.get()
. Any messages sent
since the last wakeup will rapidly be received in the order in which they were
created. If more messages were sent than can fit in the gateway's queue, the
oldest messages will be lost.
When debugging an application it is often best to start out with time.sleep()
calls as this keeps the USB interface active. When the basic design is proven,
sleep
may be replaced with deepsleep
. Applications which deepsleep may be
debugged using a UART and an FTDI adapter.
An example of a micropower publish-only application is pubonly.py
and is
listed in section 2.1.
A micropower application handling subscriptions is slow_echo.py
, listed
below:
from machine import deepsleep
from time import sleep_ms
from .link import Link
from .link_setup import gateway, channel, credentials # Args common to all nodes
try:
gwlink = Link(gateway, channel, credentials)
except OSError:
deepsleep(3_000) # Failed to connect. Out of range?
def echo(topic, message, retained):
gwlink.publish("shed", message)
gwlink.subscribe("foo_topic", 1)
gwlink.get(echo) # Get any pending messages
gwlink.close()
deepsleep(3_000)
# Now effectively does a hard reset: main.py restarts the application.
This echoes any received message to the "shed"
topic. Messages may be sent
by publishing to "foo_topic" or to "allnodes":
mosquitto_pub -h 192.168.0.10 -t allnodes -m "test message" -q 1
If power consumption is not a concern a node may be kept running continuously.
If the AP is able to change the channel, communications will fail briefly
before recovering. If a node publishes regularly and checks for success there
is a likelihood of duplicate publications. This occurs because, when using an
incorrect channel, ESPNow reports failure when the message has been
successfully sent. The fail status from link.publish
causes the application
to repeat the publication.
Connectivity outages can occur, for example by a node moving out of range.
These may be detected by checking the return value of link.publish()
or by
periodically issuing link.ping()
.
Asynchronous applications are assumed to be continuously running because
asyncio has no deepsleep
compatibility. Where the AP may change channels the
preferred solution is, in link_setup.py
, to set channel=None
and to
populate the credentials
tuple. Channel changes are then tracked
automatically.
The gateway communicates with the broker via an mqtt_as
client. This supports
the last will and clean session mechanisms which are instructions to the broker
on how to behave in response to client outages. To enable the broker to detect
outages the client sends messages to the broker at regular intervals.
The broker has no concept of nodes attached to a client. Thus a will
entry in
the config
dictionary applies to the gateway, not to any of its nodes. Adding
node level mechanisms would require the gateway to act as a mini-broker. It
would complicate node scripts and would conflict with micropower operation (by
requiring regular ping messages). It would also require node scripts to be
asynchronous.
I have no plan to implement these.
When a node publishes a message the retain
flag works normally: the broker
retains the message for future subscribers if qos==1
. See the MQTT spec. For
publications to a node, retain
does not work as it applies to the gateway
rather than to the node. The gateway assumes that an absent node is asleep. All
messages destined to it are queued and will be forwarded to the node when it
wakes.
Where a target has limited power available there are measures which can be taken to reduce the energy consumed each time it wakes from deepsleep.
In my testing any gains from precompiling to .mpy
files are too small to
measure. The connection algorithms were
tested by running the pubonly
demo on an ESP32_S3 with the following results:
Algorithm | Time (s) | Charge (mC) |
---|---|---|
Fixed | 1.8 | 112 |
Query (ch 12) | 2.2 | 153 |
WiFi | 4.2 | 300 |
To get a figure for battery consumption, consider a node with deepsleep current of 50μA publishing once per hour where the channel is fixed. There are ~8760 hours in a year. A mC is a mA for 1s, hence 1/3600 of a mAH. Consumption in mAH over a year is thus:
8760*(50/1000 + 112/3600) = 710mAH
See this repo from @glenn20 for a much more detailed exposition including ways to radically reduce energy used on boot.
For comparison I tested an ESP8266 Feather Huzzah: this board is suited for micropower use as it lacks a USB chip. Running the same demo on a fixed channel it used 71mC over 2s. Deepsleep current was 102μA. One year power was therefore:
8760*(102/1000 + 71/3600) = 1066mAH
The following images show the current profile for the three mens of channel
definition. Fixed channel:
WiFi connect takes a high current for 4s.
This chip can suffer from erratic ESPNow behaviour owing to interference between the transmitter and the on-chip USB interface. This can be worked round by using high channel numbers and reduced transmit power. See this message from a hardware manufacturer. Later releases of this particular board have addressed this issue.