ESP-NOW ユニキャスト マルチキャスト ブロードキャスト

> _今回の記事は MicroPython でのコード例を示します。_

https://docs.micropython.org/en/latest/library/espnow.html#espnow.ESPNow

# [ESP-NOW](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/network/esp_now.html) とは

[`Espressif`社](https://www.espressif.com) が [ESP32 および ESP8266](https://www.espressif.com/en/products/modules) デバイスで提供する **コネクションレス** の無線通信プロトコルです。


1回当たり最大250バイトのメッセージを、ユニキャスト、マルチキャスト、ブロードキャストにて送受信できます。


### ユニキャスト、マルチキャスト、ブロードキャスト とは

<dl>
  <dt>・ユニキャスト</dt>
  <dd>単一のアドレスを指定して、1対1で行われるデータ通信<br>　</dd>
  <dt>・マルチキャスト</dt>
  <dd>複数のアドレスを指定して、1対複数で行われるデータ通信<br>　</dd>
  <dt>・ブロードキャスト</dt>
  <dd>同じネットワーク内の全宛先を指定し、1対不特定多数で行われるデータ通信<br>　</dd>
</dl>

# アドレス

ESP-NOW においては、デバイスの`MACアドレス`が該当します。

6バイトの`bytearray`で定義します。
　例：`b'\x66\xb6\xb3\x22\x6c\xec'`

`MACアドレス`は、[`esptool`](https://docs.espressif.com/projects/esptool/en/latest/esp32/installation.html)で確認できます。
（真ん中へんの`MAC:`のところです）

> _何らかのファームウエアをインストール済みのデバイスの場合は、デバイスを`ダウンロードモード`にして現れる COMポート を指定する必要があります。_


```terminal
$ esptool.py --chip auto --port /dev/cu.usbmodem1201 flash_id

esptool.py v4.7.0
Serial port /dev/cu.usbmodem1201
Connecting...
Detecting chip type... ESP32-S3
Chip is ESP32-S3 (QFN56) (revision v0.1)
Features: WiFi, BLE, Embedded PSRAM 8MB (AP_3v3)
Crystal is 40MHz
MAC: 66:b6:b3:22:6c:ec
Uploading stub...
Running stub...
Stub running...
Manufacturer: c8
Device: 4018
Detected flash size: 16MB
Flash type set in eFuse: quad (4 data lines)
Hard resetting via RTS pin...
```


## ブロードキャスト アドレス

ESP-NOWにおいて ブロードキャスト アドレス は、`b'\xff\xff\xff\xff\xff\xff'` と定義します。


# メッセージ受信

ESP-NOW における メッセージ受信 は、自分（のMACアドレス）宛のメッセージ と、ブロードキャスト宛のメッセージを受信します。

```python
sender_addr, message = e.recv()
```

- `sender_addr` 送信元デバイスのMACアドレス
- `message`　　受信したメッセージ



# メッセージ送信

宛先のMACアドレスが1つだけの場合を、ユニキャスト送信と呼び、宛先のMACアドレスが2つ以上の場合を、マルチキャスト送信と呼びます。


## 1. ユニキャスト送信

```python
e.add_peer(addr)
e.send(message) #unicast to addr
```


## 2. マルチキャスト送信

宛先のアドレスを `add_peer()`で登録します。マルチキャスト送信は最大20宛先まで。


```python
e.add_peer(addr1)
e.add_peer(addr2)
e.send(message) #muticast to addr1, addr2
```

登録した宛先は、`del_peer()`で削除することができます。
処理途中に`add_peer()`、`del_peer()`することで、動的にマルチキャストのアドレスの組み合わせを変えたりすることができます。

```python
e.add_peer(addr1)
e.add_peer(addr2)
e.send(message) #muticast to addr1, addr2
e.del_peer(addr2)
e.add_peer(addr3)
e.send(message) #muticast to addr1, addr3
e.del_peer(addr1)
e.send(message) #unicast to addr2
```

ブロードキャストアドレス宛には送信されません。


## 3. ブロードキャスト送信

ブロードキャストアドレス宛に送信します。
ユニキャスト、マルチキャスト宛とは別に送信する必要があります。

```python
broadcast_addr = b'\xff' * 6
e.add_peer(broadcast_addr)
e.send(broadcast_addr, message)
```

自分が送信したブロードキャストメッセージを、自分が受信することはできません。


# コード例

4台のESPモジュールを使い、それぞれを`(A)`,`(B)`,`(C)`,`(D)`とする。


![es-now-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/4954aa98-b2a0-93b0-503a-28850c728e8c.png)

- ESP`(A)`はマルチキャストで、ESP`(B)`と`(D)`に送信する
- ESP`(B)`はマルチキャストで、ESP`(C)`と`(D)`に送信する
- ESP`(C)`はユニキャストで、ESP`(D)`に送信する
- ESP`(A)`,`(B)`,`(C)`,`(D)`は、ブロードキャスト宛にも送信する

> _今回のコードは すべて同期送受信で実装します。_



受信処理は、ESP32のデュアルコアを利用して専用スレッドで処理します。

```python:受信処理（共通）
import _thread
import espnow
import network
import time

from machine import RTC
rtc = RTC()

def now_str():
    year, month, day, _, hour, minute, second, _ = rtc.datetime()
    return f"{year:04}.{month:02}.{day:02} {hour:02}:{minute:02}:{second:02}"

def bytes2macaddr(b):
    return ':'.join([f"{x:02x}" for x in b])

def recvThread(e):
    while True:
        if e.any():
            addr, msg = e.recv()
            print(f'{now_str()[-8:]} from: {bytes2macaddr(addr)}, message: "{msg.decode()}"')
        else:
            time.sleep(0.01)

w = network.WLAN(network.STA_IF)
w.active(True)
print(f'my mac addr: {bytes2macaddr(w.config('mac'))}')

e = espnow.ESPNow()
e.active(True)

_thread.start_new_thread(recvThread, (e,))
```

```python:MACアドレス
# 仮のアドレスのため実在しません
addr_a = b'\x7f\xdf\xa1\xe7\xa3\x78'
addr_b = b'\x66\xb6\xb3\x22\x6c\xec'
addr_c = b'\xde\x54\x75\xd8\xf3\xd0'
addr_d = b'\x7f\xdf\xa1\xe7\xa6\xcc'

broadcast_addr = b'\xff' * 6
```

起動10秒後から1秒ごとに3回のメッセージを送信します。


```python:ESP(A)
import espnow
import time

e = espnow.ESPNow()

e.add_peer(broadcast_addr)

e.add_peer(addr_b)
e.add_peer(addr_d)

time.sleep(10)

for n in range(1, 4):
    e.send(f"from (A) multicast messgae {n}")
    time.sleep(1)
    e.send(broadcast_addr, f"from (A) broadcast messgae {n}")
    time.sleep(1)
```

```python:ESP(B)
import espnow
import time

e = espnow.ESPNow()

e.add_peer(broadcast_addr)

e.add_peer(addr_c)
e.add_peer(addr_d)

time.sleep(10)

for n in range(1, 4):
    e.send(f"from (B) multicast messgae {n}")
    time.sleep(1)
    e.send(broadcast_addr, f"from (B) broadcast messgae {n}")
    time.sleep(1)
```

```python:ESP(C)
import espnow
import time

e = espnow.ESPNow()

e.add_peer(broadcast_addr)

e.add_peer(addr_d)

time.sleep(10)

for n in range(1, 4):
    e.send(f"from (C) unicast messgae {n}")
    time.sleep(1)
    e.send(broadcast_addr, f"from (C) broadcast messgae {n}")
    time.sleep(1)
```

```python:ESP(D)
import espnow
import time

e = espnow.ESPNow()

e.add_peer(broadcast_addr)

time.sleep(10)

for n in range(1, 4):
    e.send(broadcast_addr, f"from (D) broadcast messgae {n}")
    time.sleep(1)
```


## 結果

それぞれのESPをほぼ同じタイミングで起動したときの結果です。

```terminal:ESP(A)
my mac addr: 7f:df:a1:e7:a3:78
00:01:35 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 1"
00:01:37 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 1"
00:01:37 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 1"
00:01:37 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 2"
00:01:38 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 2"
00:01:39 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 2"
00:01:39 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 3"
00:01:39 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 3"
00:01:41 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 3"
```

```terminal:ESP(B)
my mac addr: 66:b6:b3:22:6c:ec
00:01:32 from: 7f:df:a1:e7:a3:78, message: "from (A) multicast messgae 1"
00:01:33 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 1"
00:01:34 from: 7f:df:a1:e7:a3:78, message: "from (A) multicast messgae 2"
00:01:35 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 2"
00:01:36 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 1"
00:01:36 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 1"
00:01:36 from: 7f:df:a1:e7:a3:78, message: "from (A) multicast messgae 3"
00:01:37 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 2"
00:01:37 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 3"
00:01:38 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 2"
00:01:38 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 3"
00:01:40 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 3"
```

```terminal:ESP(C)
my mac addr: de:54:75:d8:f3:d0
00:01:34 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 1"
00:01:34 from: 66:b6:b3:22:6c:ec, message: "from (B) multicast messgae 1"
00:01:35 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 1"
00:01:36 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 2"
00:01:36 from: 66:b6:b3:22:6c:ec, message: "from (B) multicast messgae 2"
00:01:37 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 1"
00:01:37 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 2"
00:01:38 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 2"
00:01:38 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 3"
00:01:38 from: 66:b6:b3:22:6c:ec, message: "from (B) multicast messgae 3"
00:01:39 from: 7f:df:a1:e7:a6:cc, message: "from (D) broadcast messgae 3"
00:01:39 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 3"
```

```terminal:ESP(D)
my mac addr: 7f:df:a1:e7:a6:cc
00:01:33 from: 7f:df:a1:e7:a3:78, message: "from (A) multicast messgae 1"
00:01:34 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 1"
00:01:34 from: 66:b6:b3:22:6c:ec, message: "from (B) multicast messgae 1"
00:01:35 from: 7f:df:a1:e7:a3:78, message: "from (A) multicast messgae 2"
00:01:35 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 1"
00:01:35 from: de:54:75:d8:f3:d0, message: "from (C) unicast messgae 1"
00:01:36 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 2"
00:01:36 from: 66:b6:b3:22:6c:ec, message: "from (B) multicast messgae 2"
00:01:36 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 1"
00:01:37 from: 7f:df:a1:e7:a3:78, message: "from (A) multicast messgae 3"
00:01:37 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 2"
00:01:37 from: de:54:75:d8:f3:d0, message: "from (C) unicast messgae 2"
00:01:38 from: 7f:df:a1:e7:a3:78, message: "from (A) broadcast messgae 3"
00:01:38 from: 66:b6:b3:22:6c:ec, message: "from (B) multicast messgae 3"
00:01:38 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 2"
00:01:39 from: 66:b6:b3:22:6c:ec, message: "from (B) broadcast messgae 3"
00:01:39 from: de:54:75:d8:f3:d0, message: "from (C) unicast messgae 3"
00:01:40 from: de:54:75:d8:f3:d0, message: "from (C) broadcast messgae 3"
```

それぞれ、狙ったとおりに受信できています。

# まとめ

ESP-NOW は、ESP32 / ESP8266 間に特化した通信であれば、お手軽に使えるプロトコルです。

  - WiFi AP を必要としない　（併用も可）
  - ブロードキャスト送信であれば、受信側デバイスの数に制限がない（と、思われる）

<br>そういった通信が必要であれば、使ってみてはいかがでしょうか。