espnow
--- ESP-NOW 無線プロトコルのサポート¶
本モジュールは、Espressif が ESP32 および ESP8266 デバイスで提供する ESP-NOW プロトコルへのインタフェースを提供します(API ドキュメント)。
目次¶
はじめに¶
ESP-NOWは、以下をサポートするコネクションレスの無線通信プロトコルです。
最大20の登録されたピア間の直接通信:
無線アクセスポイント(AP)の必要なし
暗号化された通信と暗号化されていない通信(最大6つの暗号化されたピア)
最大250バイトのメッセージサイズ
ESP32 および ESP8266 デバイスの Wifi 操作(network.WLAN)と並行して動作可能
ESP-NOW は、小規模な IoT ネットワーク、レイテンシーを短くしたいアプリケーションや消費電力を抑えたいアプリケーション(バッテリー駆動デバイスなど)、デバイス間の長距離通信(数百メートル)に特に有用です。
このモジュールは、ピアデバイスの Wifi 信号強度(RSSI)の追跡もサポートしています。
簡単な例は次のようになります:
送信側:
import network
import espnow
# A WLAN interface must be active to send()/recv()
sta = network.WLAN(network.WLAN.IF_STA) # Or network.WLAN.IF_AP
sta.active(True)
sta.disconnect() # For ESP8266
e = espnow.ESPNow()
e.active(True)
peer = b'\xbb\xbb\xbb\xbb\xbb\xbb' # MAC address of peer's wifi interface
e.add_peer(peer) # Must add_peer() before send()
e.send(peer, "Starting...")
for i in range(100):
e.send(peer, str(i)*20, True)
e.send(peer, b'end')
受信側:
import network
import espnow
# A WLAN interface must be active to send()/recv()
sta = network.WLAN(network.WLAN.IF_STA)
sta.active(True)
sta.disconnect() # Because ESP8266 auto-connects to last Access Point
e = espnow.ESPNow()
e.active(True)
while True:
host, msg = e.recv()
if msg: # msg == None if timeout in recv()
print(host, msg)
if msg == b'end':
break
クラス ESPNow¶
コンストラクタ¶
- class espnow.ESPNow¶
シンゲルトンの ESPNow オブジェクトを返します。これはシンゲルトンであるため、
espnow.ESPNow()
の呼び出しは同じオブジェクトへの参照を返します。注釈
ESP8266 におけるコードサイズの制限や Espressif API の違いにより、一部のメソッドは ESP32 のみで利用可能です。
設定¶
- ESPNow.active([flag])¶
オプション引数
flag
の値に応じて、ESP-NOW 通信プロトコルを初期化または終了処理します。- Arguments:
flag: ブール型に変換可能な任意のPython値。
True
: ESP-NOW 通信プロトコルを使えるようにソフトウェアとハードウェアを準備します。具体的には次のことを行います。ESPNow データ構造体の初期化
受信データバッファの割り当て
esp_now_init() の呼び出し
送受信双方のコールバック登録
False
: Espressif ESP-NOW ソフトウェアスタックの終了処理(esp_now_deinit())、コールバック無効化、受信データバッファ解放、すべてのピアの登録解除をします。
flag が提供されていない場合、ESPNow インタフェースの現在の状態を返します。
- Returns:
インタフェースが現在アクティブな場合は
True
、それ以外の場合はFalse
を返します。
- ESPNow.config(param=value, ...)¶
- ESPNow.config('param') (ESP32 only)
ESPNow インタフェースの設定値を設定または取得します。値を設定するには、キーワード構文を使用し、一度に1つ以上のパラメータを設定できます。値を取得するには、パラメータ名を文字列で引用符で囲み、1つのパラメータのみで問い合わせます。
注記: ESP8266ではパラメータの 取得 をサポートしていません。
- Options:
rxbuf: (デフォルト=526) ESPNow パケットデータを格納するために使う内部バッファのサイズをバイト単位で取得または設定します。デフォルトのサイズは、関連する mac_address (6バイト)、メッセージバイト数(1バイト)、RSSI データとバッファオーバーヘッドを持つ最大サイズの2つの ESPNow パケットに合わせて選択されます。多数の大きなパケットを受信する場合や、バースト状態のトラフィックが予想される場合は、この値を増やしてください。
注記 : 受信バッファは
ESPNow.active()
によって割り当てられます。この値を変更しても、次のESPNow.active(True)
の呼び出しが行われるまで効果はありません。timeout_ms: (デフォルト=300,000) ESPNowメッセージを受信するためのデフォルトのタイムアウト値(ミリ秒単位)。 timeout_ms が負の値の場合は無期限に待機します。タイムアウトは
recv()
/irecv()
/recvinto()
の引数としても指定できます。rate: (ESP32のみ、IDF>=4.3.0のみ) ESPNow パケットの送信速度を設定します。enum wifi_phy_rate_t 列挙値のいずれかに設定する必要があります。
- Returns:
パラメータ値の取得の場合はその値、そうでない場合は
None
を返します。
- Raises:
初期化されない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_INIT")
が発生。無効な設定オプションまたは値が指定された場合、
ValueError()
が発生。
データの送受信¶
メッセージを送受信する前に wifi インタフェース(network.WLAN.IF_STA
または network.WLAN.IF_AP
)を active()
にする必要がありますが、WLAN インタフェースを接続したり設定する必要はありません。 たとえば次のようにします:
import network
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.disconnect() # ESP8266 用
注記: ESP8266 には、 active(True)
に設定すると(再起動/リセット後でも)自動的に直前に接続していた wifi アクセスポイントに再接続する 特性 があります。これにより、ESP-NOW メッセージの受信信頼性が低下します(ESPNow and Wifi Operation を参照)。 active(True)
の後に disconnect()
を呼び出すことで、これを回避できます。
- ESPNow.send(mac, msg[, sync])¶
- ESPNow.send(msg) (ESP32 only)
msg
に含まれるデータを、指定したネットワークmac
アドレスを持つピアに送信します。2番目の形式では、mac=None
とsync=True
になります。ピアは、メッセージを送信する前にESPNow.add_peer()
で登録しておく必要があります。- Arguments:
mac: 長さが厳密に
espnow.ADDR_LEN
(6バイト)であるバイト列かNone
。 mac がNone
の場合(ESP32 のみ)、ブロードキャストまたはマルチキャスト MAC アドレスを除くすべての登録されたピアにメッセージを送信します。msg: 最大長が
espnow.MAX_DATA_LEN
(250)である文字列またはバイト列。sync:
True
: (デフォルト) メッセージをピアに送信し、応答を待機します(応答がない場合もあり)False
: メッセージを送信し、すぐに返信します。ピアからの応答は破棄されます。
- Returns:
sync=False
またはsync=True
かつすべてのピアから応答があった場合はTrue
、さもなければFalse
。
- Raises:
初期化されない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_INIT")
が発生。ピアが登録されていない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_FOUND")
が発生。wifi インタフェース が
active()
でなければ、OSError(num, "ESP_ERR_ESPNOW_IF")
が発生。内部の ESP-NOW バッファが一杯になると、
OSError(num, "ESP_ERR_ESPNOW_NO_MEM")
が発生。パラメータの値が不正な場合は
ValueError()
が発生。
注記: ピアが ESP-NOW システムを初期化したか、ESP-NOW トラフィックをアクティブにリスンしているかどうかに関係なく、ピアの wifi インタフェースが
active()
であり、同じチャンネルに設定されていれば、ピアは成功で応答します(Espressif の ESP-NOW ドキュメントを参照)。
- ESPNow.recv([timeout_ms])¶
メッセージが送信されてくるのを待機し、送信元の
mac
アドレスとメッセージを返します。 注記: ピアからメッセージを受信するために、(add_peer()
を使って)ピアを登録しておく必要はありません。- Arguments:
timeout_ms: (オプション): 次の値を指定できます。
0
: タイムアウトなし。受信可能なデータがない場合はすぐに戻ります。> 0
: ミリ秒単位のタイムアウト値を指定します。< 0
: タイムアウトしない、すなわち新しいメッセージを受信するまで永遠に待機します。None
(または指定なし):ESPNow.config()
で設定したデフォルトのタイムアウト値を使用します。
- Returns:
メッセージを受信する前にタイムアウトが発生した場合は
(None、None)
が返ります。[mac、msg]
は次のものです:mac
はメッセージを送信したデバイスのアドレスを含むバイト列msg
はメッセージを含むバイト
- Raises:
初期化されない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_INIT")
が発生。wifi インタフェースが
active()
されていない場合、OSError(num, "ESP_ERR_ESPNOW_IF")
が発生。timeout_ms の値が無効な場合、
ValueError()
が発生。
ESPNow.recv()
は、返されるリストおよびpeer
とmsg
のバイト列のために新しいストレージを割り当てます。このため、データレートが高い場合にメモリの断片化を引き起こす可能性があります。メモリに優しい代替手段としてESPNow.irecv()
を参照してください。
- ESPNow.irecv([timeout_ms])¶
ESPNow.recv()
と同様に機能しますが、返される値[mac、msg]
を格納するために内部のバイト配列を再利用するため、各呼び出しで新しいメモリが割り当てられることはありません。- Arguments:
timeout_ms: (オプション) ミリ秒単位のタイムアウト値(
ESPNow.recv()
を参照)。
- Returns:
ESPNow.recv()
と違い、msg
はバイト列ではなく bytearray です。ESP8266 ではmac
も bytearray になります。
- Raises:
ESPNow.recv()
を参照。
注記: ESPNow オブジェクトを反復処理してもメッセージを読み取れます。この方法では、メモリ割当て/解放の対策として
irecv()
メソッドを使っています。たとえば次のように使います:import espnow e = espnow.ESPNow(); e.active(True) for mac, msg in e: print(mac, msg) if mac is None: # mac, msg will equal (None, None) on timeout break
- ESPNow.recvinto(data[, timeout_ms])¶
送信されてくるメッセージを待機し、メッセージの長さをバイト数で返します。これは
recv()
とirecv()
の両方で使われる低レベルメソッドです。- Arguments:
data: 少なくとも2つの要素
[peer、msg]
からなるリスト。msg
はメッセージを保持するために十分な大きさの bytearray である必要があります(250バイト)。ESP8266で、peer
は6バイトの bytearray である必要があります。これらの bytearray には、送信元の MAC アドレスとメッセージが格納されます(後述の ESP32 についての注記を参照)。timeout_ms: (オプション) ミリ秒単位のタイムアウト値(
ESPNow.recv()
を参照)。
- Returns:
メッセージのバイト数。メッセージが受信される前に timeout_ms が経過した場合は 0 を返します。
- Raises:
ESPNow.recv()
を参照。
注記: ESP32の場合:
data
リストの最初の要素に bytearray を提供する必要はありません。これは、ピアデバイステーブル (ESPNow.peers_table
を参照)内の一意のピアアドレスへの参照に置換されます。リストが少なくとも4つの要素である場合、rssi およびタイムスタンプ値が3番目および4番目の要素として保存されます。
- ESPNow.any()¶
ESPNow.recv()
で読み取れるデータがあるかをチェックします。より高度な文字のクエリングには、
select.poll()
を使ってください:import select import espnow e = espnow.ESPNow() poll = select.poll() poll.register(e, select.POLLIN) poll.poll(timeout)
- Returns:
データが読み取れる場合は
True
、そうでない場合はFalse
を返します。
- ESPNow.stats() (ESP32 only)¶
- Returns:
送信/受信/損失したパケット数を持つ、次の5要素のタプルを返します:
(tx_pkts, tx_responses, tx_failures, rx_packets, rx_dropped_packets)
受信バッファが一杯になると、着信パケットが 破棄 されます。パケット損失を減らすには、
rxbuf
構成パラメータを増やし、できるだけ速くメッセージを読み取るようにしてください。注記: パケットが破棄されても、送信側としては受信されたとみなします。
ピア管理¶
ESP32デバイスでは、Espressif ESP-NOW ソフトウェアがメッセージを send()
する前に相手のデバイス(ピア)を add_peer()
で 登録 しておく必要があります(これは、ESP8266 デバイスでは強制 されません )。ピアから暗号化されていないメッセージを受信するのであれば、ピアの登録は 要りません 。
暗号化されたメッセージ: 暗号化されたメッセージを受信するには、受信側のデバイスがまず送信者を登録し、送信者と同じ暗号化キー(PMKとLMK)を使う必要があります(set_pmk()
と add_peer()
を参照。
- ESPNow.set_pmk(pmk)¶
メッセージ暗号化のためのローカルマスターキー(LMK: Local Master Key)を暗号化するのに使うプライマリマスターキー(PMK: Primary Master Key)を設定します。設定しない場合、Espressif ESP-NOW ソフトウェアスタックによってデフォルトの PMK が使われます。
注記: メッセージは
ESPNow.add_peer()
で lmk が設定されている場合にのみ暗号化されます(Security を参照)。- Arguments:
pmk:
espnow.KEY_LEN
(16バイト)の長さのバイト列、bytearray、または文字列である必要があります。
- Returns:
None
- Raises:
pmk 値が不正な場合、
ValueError()
が発生。
- ESPNow.add_peer(mac[, lmk][, channel][, ifidx][, encrypt])¶
- ESPNow.add_peer(mac, param=value, ...) (ESP32 only)
指定した MAC アドレスをピアとして追加/登録します。追加のパラメータを位置引数またはキーワード引数として指定することもできます(パラメータに
None
を指定するとデフォルト値が適用されます)。- Arguments:
mac: ピアのMACアドレス(6バイトのバイト列)。
lmk: このピアとのデータ転送を暗号化するために使うローカルマスターキー(LMK)キー(encrypt パラメータが
False
に設定されていない場合に限ります)。次のいずれかである必要があります。長さが
espnow.KEY_LEN
(16バイト)のバイト列、bytearray、または文字列True
以外の任意のPython値(デフォルト=b''
)。これは暗号化を無効にする空のキーを示します。
channel: このピアと通信するための WiFi チャンネル(2.4GHz)。0 から 14 までの整数でなければなりません。チャネルが 0 に設定されている場合、WiFi デバイスの現在のチャネルが使用されます。(デフォルト=0)
ifidx: (ESP32 のみ) このピアにデータを送信するために使う WiFi インタフェースのインデックス。
network.WLAN.IF_STA
(=0)またはnetwork.WLAN.IF_AP
(=1)に設定された整数でなければなりません。(デフォルト=0/network.WLAN.IF_STA
)。詳細については、後述の ESPNow and Wifi Operation を参照してください。encrypt: (ESP32 のみ) このピアと交換されるデータが PMK と LMK で暗号化されるようにするには
True
に設定します。(lmk に適正値が指定されていればデフォルトはTrue
、さもなければFalse
)
ESP8266: ESP8266 ではキーワード引数を指定できません。
注記: 登録できるピアの最大数は 20 (
espnow.MAX_TOTAL_PEER_NUM
)で、そのうち最大 6 (espnow.MAX_ENCRYPT_PEER_NUM
) は暗号化が有効なピアです(Espressif APIドキュメントの ESP_NOW_MAX_ENCRYPT_PEER_NUM を参照)。
- Raises:
初期化されない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_INIT")
が発生。mac がすでに登録されている場合、
OSError(num, "ESP_ERR_ESPNOW_EXIST")
が発生。登録するピアが多すぎる場合、
OSError(num, "ESP_ERR_ESPNOW_FULL")
が発生。キーワード引数または値が不正な場合、
ValueError()
が発生。
- ESPNow.del_peer(mac)¶
指定した mac アドレスに関連付けられたピアを登録解除します。
- Returns:
None
- Raises:
初期化されない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_INIT")
が発生。mac が登録されていない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_FOUND")
が発生。mac が不正な場合、
ValueError()
が発生。
- ESPNow.get_peer(mac) (ESP32 only)¶
登録されているピアに関する情報を返します。
- Returns:
(mac, lmk, channel, ifidx, encrypt)
: 指定した mac アドレスに関連付けられた「ピア情報」のタプル。
- Raises:
初期化されない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_INIT")
が発生。mac が登録されていない場合、
OSError(num, "ESP_ERR_ESPNOW_NOT_FOUND")
が発生。mac が不正な場合、
ValueError()
が発生。
- ESPNow.peer_count() (ESP32 only)¶
登録されているピア数を返します:
(peer_num, encrypt_num)
:peer_num
は登録されているピア数。encrypt_num
は暗号化されたピア数。
- ESPNow.get_peers() (ESP32 only)¶
登録されているすべてのピアの「ピア情報」パラメータを(タプルのタプルとして)返します"
- ESPNow.mod_peer(mac, lmk, [channel], [ifidx], [encrypt]) (ESP32 only)¶
- ESPNow.mod_peer(mac, 'param'=value, ...) (ESP32 only)
指定した msc アドレスに関連付けられたピアのパラメータを更新します。パラメータは位置引数またはキーワード引数として指定できます(
ESPNow.add_peer()
を参照)。パラメータを指定しない場合(もしくはNone
指定した場合)、既存のパラメータ値を返します。
コールバックメソッド¶
- ESPNow.irq(callback) (ESP32 only)¶
別の ESPNow デバイスからメッセージを受信した直後に呼び出されるコールバック関数を設定します。コールバック関数は
ESPNow
のインスタンスオブジェクトを引数として呼び出されます。より信頼性の高い運用のために、コールバックが呼び出されたときに利用可能な限り多くのメッセージを読み込み、読み込みのタイムアウトをゼロに設定することをお勧めします。たとえば、次のように使います:def recv_cb(e): while True: # バッファで待機しているすべてのメッセージを読み込む mac, msg = e.irecv(0) # メッセージが残っていなければ待たない if mac is None: return print(mac, msg) e.irq(recv_cb)
irq()
コールバックメソッドは、主にデータレートが適度でデバイスがあまり忙しくない場合に、着信したメッセージを処理するための代替方法ですが、いくつかの注意点があります:パケットが十分なレートで到着している場合や、他の MicroPython コンポーネント(例: bluetooth, machine.Pin.irq(), machine.timer, i2s, ...)がスケジューラスタックを使用している場合、スケジューラスタックがオーバーフローして、コールバックが失敗する可能性があります。このメソッドは、メッセージのバースト処理や高スループット、他のハードウェア操作に忙しいデバイスの処理に対しては、信頼性が低い場合があります。
スケジュールされた関数コールバックの詳細については
micropython.schedule()
を参照してください。
定数¶
例外¶
Espressif ESP-NOW ソフトウェアスタックがエラーコードを返した場合、MicroPython espnow モジュールは OSError(errnum, errstring)
例外を発生させます。ここで、 errstring
は Espressif ESP-NOW ドキュメント にあるエラーコードの名前に設定されます。次に例を示します:
try:
e.send(peer, 'Hello')
except OSError as err:
if len(err.args) < 2:
raise err
if err.args[1] == 'ESP_ERR_ESPNOW_NOT_INIT':
e.active(True)
elif err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND':
e.add_peer(peer)
elif err.args[1] == 'ESP_ERR_ESPNOW_IF':
network.WLAN(network.WLAN.IF_STA).active(True)
else:
raise err
Wifi 信号強度 (RSSI) - (ESP32 のみ)¶
ESPNow オブジェクトは、すべてのホストからの最後の受信メッセージの信号強度とタイムスタンプを含む ピアデバイステーブル を管理しています。ピアデバイステーブル には ESPNow.peers_table
を使ってアクセスでき、ピアデバイスのネットワーク内でデバイスの近接性を追跡し、 最も近い隣接デバイス を特定するために使えます。この機能は ESP8266 デバイスでは使えません。
- ESPNow.peers_table¶
ピアデバイステーブル への参照: 既知のピアデバイスと RSSI 値の辞書です。
{peer: [rssi, time_ms], ...}
ここでの各項目は次のとおりです:
peer
はピアの MAC アドレス(bytes
)です。rssi
はピアからの最新の受信メッセージの wifi 信号強度です(dBm: -127 から 0 の範囲)。time_ms
はメッセージの受信時刻です(システム起動からのミリ秒単位の経過時間、12日ごとにラップ)。
サンプルコード:
>>> e.peers_table {b'\xaa\xaa\xaa\xaa\xaa\xaa': [-31, 18372], b'\xbb\xbb\xbb\xbb\xbb\xbb': [-43, 12541]}
注記:
recv()
によって返されるmac
アドレスは、ピアデバイステーブルのピアキーの値への参照です。注記: デバイステーブル内の RSSI とタイムスタンプの値は、アプリケーションによってメッセージが読み取られたときにのみ更新されます。
asyncio のサポート¶
追加のモジュール(aioespnow
)が利用可能であり、 asyncio をサポートします。
注記: Asyncio サポートは、すべての ESP32 ターゲットで使えます。また、asyncio モジュールを備えた ESP8266 ボード(すなわち、少なくとも2MBのフラッシュメモリを持つ ESP8266 デバイス)でも使えます。
次は小さな非同期サーバーの例です:
import network
import aioespnow
import asyncio
# A WLAN interface must be active to send()/recv()
network.WLAN(network.WLAN.IF_STA).active(True)
e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support
e.active(True)
peer = b'\xbb\xbb\xbb\xbb\xbb\xbb'
e.add_peer(peer)
# Send a periodic ping to a peer
async def heartbeat(e, peer, period=30):
while True:
if not await e.asend(peer, b'ping'):
print("Heartbeat: peer not responding:", peer)
else:
print("Heartbeat: ping", peer)
await asyncio.sleep(period)
# Echo any received messages back to the sender
async def echo_server(e):
async for mac, msg in e:
print("Echo:", msg)
try:
await e.asend(mac, msg)
except OSError as err:
if len(err.args) > 1 and err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND':
e.add_peer(mac)
await e.asend(mac, msg)
async def main(e, peer, timeout, period):
asyncio.create_task(heartbeat(e, peer, period))
asyncio.create_task(echo_server(e))
await asyncio.sleep(timeout)
asyncio.run(main(e, peer, 120, 10))
- async AIOESPNow.arecv()
ESPNow.recv()
の Asyncio サポート。このメソッドは引数としてタイムアウト値を取りません。
- async AIOESPNow.airecv()
ESPNow.irecv()
の Asyncio サポート。このメソッドは引数としてタイムアウト値を取りません。
- async AIOESPNow.asend(mac, msg, sync=True)
- async AIOESPNow.asend(msg)
ESPNow.send()
の Asyncio サポート。
ブロードキャストとマルチキャスト¶
すべてのアクティブな ESPNow クライアントは、自身の MAC アドレス宛てに送信されたメッセージを受信します。また、(ESP8266 デバイスを除く)すべてのデバイスは ブロードキャスト MAC アドレス(b'\xff\xff\xff\xff\xff\xff'
)またはマルチキャスト MAC アドレス宛てに送信されたメッセージも受信します。
すべての ESPNow デバイス(ESP8266デバイスを含む)は、ブロードキャスト MAC アドレスまたは任意のマルチキャスト MAC アドレスにもメッセージを送信できます。
ブロードキャストメッセージを send()
で送信するには、最初に add_peer()
を使ってブロードキャスト(またはマルチキャスト) MAC アドレスを登録する必要があります。send()
は、ブロードキャストの場合は常に True
を返しますが、メッセージを受信するデバイスがあるかどうかは問いません。ブロードキャストアドレスまたはマルチキャストアドレス宛てに送信されたメッセージは暗号化できません。
注記: ESPNow.send(None, msg)
はブロードキャストアドレスを 除く すべての登録済みピアに送信します。ブロードキャストまたはマルチキャストメッセージを送信するには、ブロードキャスト(またはマルチキャスト) MAC アドレスをピアとして指定する必要があります。たとえば次のようにします:
bcast = b'\xff' * 6
e.add_peer(bcast)
e.send(bcast, "Hello World!")
ESPNow と Wifi 操作¶
ESPNow メッセージは、 active()
にした 任意の WLAN
インターフェース(network.WLAN.IF_STA
または network.WLAN.IF_AP
)で送受信できます。このインターフェースが Wifi ネットワークに接続されているか、アクセスポイントとして設定されている場合でもESPNow 通信は可能です。ESP32 または ESP8266 デバイスが Wifi アクセスポイントに接続すると(ESP32 Quickref を)、次のようなことが起こり、ESPNow 通信に影響を与えます:
Wifi パワーセービングモード(
network.WLAN.PM_PERFORMANCE
)が自動的にアクティベートされる。esp デバイスの無線機が、アクセスポイントで使用されているチャンネルに合わせて変更される。
Wifi パワーセービングモード: Espressif ドキュメント を参照) パワーセービングモードでは、デバイスが定期的に(通常は数百ミリ秒)無線機をオフにし、ESPNow メッセージの受信が信頼性に欠ける状態になります。これに対処する方法は次のいずれかです:
STA_IF インタフェースでパワーセービングモードを無効化
sta.config(pm=sta.PM_NONE)
を使用
AP_IF インタフェースをオンにすることで、パワーセービングモードが無効になります。ただし、デバイスはアクティブな Wifi アクセスポイントをアドバタインジングすることになります。
メッセージを AP_IF インターフェース経由で送信することもできますが、これは必須ではありません。
ESP8266 のピアは、この AP_IF インターフェースにメッセージを送信する必要があります(後述)。
ESPNow クライアントのメッセージ送信のリトライ設定を行うこと。
ESP8266 デバイスからのメッセージの受信: 奇妙なことに、Wifi ネットワークに接続された ESP32 デバイス(上記の方法1または2を使用)は、別の ESP32 デバイスから STA_IF MAC アドレス宛てに送信された ESPNow メッセージを受信しますが、ESP8266 デバイスからのメッセージは拒否します。ESP8266 デバイスからメッセージを受信するには、AP_IF インターフェースを active(True)
に設定し、 さらに 、メッセージをAP_IF MAC アドレス宛てに送信する必要があります。
Wifi チャンネルの管理: Wifi アクセスポイントに接続されているデバイスと通信したい他の ESPNow デバイスは、同じチャンネルを使う必要があります。一般的なシナリオとしては、1つの ESPNow デバイスが Wifi ルーターに接続し、ESPNow を介して接続されたセンサーグループからのメッセージのプロキシとして機能することです:
プロキシ:
import network, time, espnow
sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected
sta.connect('myssid', 'mypassword')
while not sta.isconnected(): # Wait until connected...
time.sleep(0.1)
sta.config(pm=sta.PM_NONE) # ..then disable power saving
# Print the wifi channel used AFTER finished connecting to access point
print("Proxy running on channel:", sta.config("channel"))
e = espnow.ESPNow(); e.active(True)
for peer, msg in e:
# Receive espnow messages and forward them to MQTT broker over wifi
センサー:
import network, espnow
sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected
sta.config(channel=6) # Change to the channel used by the proxy above.
peer = b'0\xaa\xaa\xaa\xaa\xaa' # MAC address of proxy
e = espnow.ESPNow(); e.active(True);
e.add_peer(peer)
while True:
msg = read_sensor()
e.send(peer, msg)
time.sleep(1)
ESPNow を Wifi と併用する際に注意するべき他の問題は次のとおりです:
起動時に WIFI を既知の状態に設定する: MicroPython はソフトリセット後に wifi ペリフェラルをリセットしません。これにより予期しない動作が発生する可能性があります。ソフトリセット後に wifi を既知の状態にリセットするために、STA_IF と AP_IF を起動時に所望の状態に設定する前に非アクティブにするようにしてください。たとえば次のようにします:
import network, time def wifi_reset(): # Reset wifi to AP_IF off, STA_IF on and disconnected sta = network.WLAN(network.WLAN.IF_STA); sta.active(False) ap = network.WLAN(network.WLAN.IF_AP); ap.active(False) sta.active(True) while not sta.active(): time.sleep(0.1) sta.disconnect() # For ESP8266 while sta.isconnected(): time.sleep(0.1) return sta, ap sta, ap = wifi_reset()
ソフトリセットは、デバイスを REPL に接続したり、
ctrl-D
を入力すると常に発生します。STA_IF と AP_IF は常に同じチャンネルで動作: AP_IF は Wifi ネットワークに接続するとチャンネルが変更されます。これは、AP_IF に設定したチャンネルに関係なく起こりま(Attention Note 3 を参照)。実際には、デバイス上には1つの Wifi 無線機しかなく、STA_IF と AP_IF の仮想デバイスで共有されています。
Wifi ルーターの自動チャンネル割当ての無効化: Wifiネットワーク用の Wifi ルーターが自動的に Wifi チャンネルを割り当てるように設定されている場合、他の Wifi ルーターからの干渉を検出した場合にネットワークのチャンネルを変更することがあります。これが発生すると、Wifi ネットワークに接続された ESP デバイスもルーターに合わせてチャンネルを変更しますが、ESPNow 専用のデバイスは以前のチャンネルのままであり、通信が失われます。これを緩和するためには、Wifi ルーターを固定の Wifi チャンネルを使用するように設定するか、デバイスを現在のチャンネルで期待されるピアを見つけることができない場合に Wifi チャンネルを再スキャンするように構成します。
MicroPython が再接続を試みる際の Wifi チャンネル再スキャン: esp デバイスが停止したWifiアクセスポイントに接続されている場合、MicroPython はアクセスポイントに再接続しようとしてチャンネルをスキャンし始めます。これにより、AP をスキャンしている間は ESPNow メッセージが失われます。
sta.config(reconnects=0)
によってこれを無効にできますが、接続が失われた後の自動再接続も無効になります。一部の ESP IDF バージョンでは、STA_IF インタフェースからの ESPNow パケットの送信は、STA_IF と同じ Wifi チャンネルに登録されたピアに対してのみ許可されます。
ESPNOW: Peer channel is not equal to the home channel, send fail!
ESPNow とスリープモード¶
machine.lightsleep([time_ms])
と machine.deepsleep([time_ms])
関数は、ESP32 および周辺機器(WiFi および Bluetooth 通信を含む)をスリープ状態にするのに使えます。これは多くのアプリケーションでバッテリー電力を節約するのに便利です。ただし、アプリケーションがライトスリープまたはディープスリープに入る前に WLAN ペリフェラルを(active(False)
を使って)無効にする必要があります(Sleep Modes を参照)。そうしない場合、WiFi 通信がスリープから復帰後に正しく初期化されない場合があります。STA_IF および AP_IF インタフェースが両方とも active(True)
に設定されている場合、どのスリープモードに入る前に両方のインターフェースを active(False)
に設定する必要があります。
例:ディープスリープ:
import network, machine, espnow
sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected
peer = b'0\xaa\xaa\xaa\xaa\xaa' # MAC address of peer
e = espnow.ESPNow()
e.active(True)
e.add_peer(peer) # Register peer on STA_IF
print('Sending ping...')
if not e.send(peer, b'ping'):
print('Ping failed!')
e.active(False)
sta.active(False) # Disable the wifi before sleep
print('Going to sleep...')
machine.deepsleep(10000) # Sleep for 10 seconds then reboot
例:ライトスリープ:
import network, machine, espnow
sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected
sta.config(channel=6)
peer = b'0\xaa\xaa\xaa\xaa\xaa' # MAC address of peer
e = espnow.ESPNow()
e.active(True)
e.add_peer(peer) # Register peer on STA_IF
while True:
print('Sending ping...')
if not e.send(peer, b'ping'):
print('Ping failed!')
sta.active(False) # Disable the wifi before sleep
print('Going to sleep...')
machine.lightsleep(10000) # Sleep for 10 seconds
sta.active(True)
sta.config(channel=6) # Wifi loses config after lightsleep()