グラスをスマートにする技術～その３：ESP-Now～

###概要：
　気になるあの人のもとに、かっこよくグラスを運びたい、そんな経験ありませんか？
　私が所属するaNo研は、遠隔操作型自走グラス「グラス・ポーター」を発表しました。
　この記事は、「グラス・ポーター」に使われている技術を紹介します。


###実施例
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">グラスを傾けるともう１つのグラスが走ります<br>グラスポーター<a href="https://twitter.com/hashtag/%E7%A7%81%E3%82%92%E5%B8%83%E6%95%99%E3%81%97%E3%81%A6?src=hash&amp;ref_src=twsrc%5Etfw">#私を布教して</a> <a href="https://t.co/u5teLYCqEW">pic.twitter.com/u5teLYCqEW</a></p>&mdash; aNo研 (@anoken2017) <a href="https://twitter.com/anoken2017/status/1062491954414383104?ref_src=twsrc%5Etfw">2018年11月13日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

###システム構成
![181125_ekanji_5.png](https://qiita-image-store.s3.amazonaws.com/0/73020/23a59164-8867-5697-5e10-0c13d9e30ff8.png)

「グラス・ポーター」は、スマートグラス「e幹事」によって遠隔操作する、自走式グラスです。
「グラス・ポーター」・「e幹事」はともに、M5Stackを搭載しており、両者の通信には、「ESP-NOW」を採用しています。


###ESP-NOW
 ESP-NOWは、短いパケットを特徴とする高速でコネクションレスな通信技術送信です。 ESP-NOWはスマートライト、リモコン、センサーなどのアプリケーションに最適です。
　ESP-NOWはIEEE802.11 Action Vendorフレーム技術とEspression if社が開発したCCMP暗号化技術により、コネクションレス通信を実現します。Wifiと比べて立ち上げ時間・通信時間がとても高速で、ロボットを制御する用途では、通信の遅延が少ないので、制御性がよくなるメリットがあります。

#####特徴
ESP-NOWは次の機能をサポートしています：
•暗号化および暗号化されていないunicast 通信。
•暗号化されたpeer deciceと暗号化されていないピアpeer deciceが混在。
•最大250バイトのペイロードを伝送。
•アプリケーション層に送信の成功または失敗を通知するために設定できる送信コールバック関数

ESP-NOW技術には次の制限もあります。
•Broadcast はサポートされていません。
•暗号化通信には限定があります。10個のの暗号化された通信はStationModeのほとんどでサポートされています。
SoftAPまたはSoftAP +StationModeの場合は、最大で6です。複数の暗号化されていない通信もサポートしていますが、暗号化通信を含めて、総数は20以下でないといけません。
•ペイロードは250バイトに制限されています。

#####制御フロー

######Master
    ステップ1：Master上のESPNow を初期化し、STAモードで設定
    ステップ2：スレーブESP32のスキャンを開始します（スレーブのSSIDの先頭語に`slave 'を追加してセットアップしてください）
    ステップ3：発見されたら、ピアとしてスレーブを追加します
    ステップ4：コールバックを送信するための登録しおます
    ステップ5：MasterからSlaveへのデータ送信を開始します。

######Slave
   ステップ1：SlaveのESPNow  を初期化
    ステップ2：SlaveのSSIDを `slave 'という先頭語で更新する
    ステップ3：APモードでSlaveを設定する
    ステップ4：受信コールバックに登録してデータを待つ
    ステップ5：データが到着したら、シリアルモニタで印刷します

注：MasterとSlaveはセットアップを簡単に理解できるように定義しました。ESPNOW APIには、MasterとSlaveの概念はありません。どのデバイスもMasterもしくはSlaveとして機能することができます。


####実装例
2個のM5Stackを片方をSlave、片方をMasterとして通信を行うサンプルです。
2個のM5Stackに書き込み、起動することで、通信が始まります。

######Slave

```
#include <esp_now.h>
#include <WiFi.h>
#define CHANNEL 1
void InitESPNow() {
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }
}

void configDeviceAP() {
  char* SSID = "Slave_1";
  bool result = WiFi.softAP(SSID, "Slave_1_Password", CHANNEL, 0);
  if (!result) {
    Serial.println("AP Config failed.");
  } else {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("ESPNow/Basic/Slave Example");
  WiFi.mode(WIFI_AP);  //Set device in AP mode to begin with
  configDeviceAP();  // configure device AP mode
  Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());  // This is the mac address of the Slave in AP Mode
  InitESPNow();  // Init ESPNow with a fallback logic
  esp_now_register_recv_cb(OnDataRecv);  // Once ESPNow is successfully Init, we will register for recv CB to get recv packer info.

}

void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.print("Last Packet Recv from: "); Serial.println(macStr);
  Serial.print("Last Packet Recv Data: "); Serial.println(*data);
  Serial.println("");
}

void loop() {
	delay(10);
}

```

######Master

```

#include <esp_now.h>
#include <WiFi.h>

esp_now_peer_info_t slave;
#define CHANNEL 3
#define PRINTSCANRESULTS 0
#define DELETEBEFOREPAIR 0

void InitESPNow() {
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }
}

void ScanForSlave() {
  int8_t scanResults = WiFi.scanNetworks();
  bool slaveFound = 0;  // reset on each scan
  memset(&slave, 0, sizeof(slave));
  Serial.println("");
  if (scanResults == 0) {
    Serial.println("No WiFi devices in AP Mode found");
  } else {
    Serial.print("Found "); Serial.print(scanResults); Serial.println(" devices ");
    for (int i = 0; i < scanResults; ++i) {
      String SSID = WiFi.SSID(i);
      int32_t RSSI = WiFi.RSSI(i);
      String BSSIDstr = WiFi.BSSIDstr(i);
     delay(10);
      if (SSID.indexOf("Slave") == 0) {
        Serial.println("Found a Slave.");
        Serial.print(i + 1); Serial.print(": "); Serial.print(SSID); Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]"); Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
        // Get BSSID => Mac Address of the Slave
        int mac[6];
        if ( 6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x%c",  &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5] ) ) {
          for (int ii = 0; ii < 6; ++ii ) {
            slave.peer_addr[ii] = (uint8_t) mac[ii];
          }
        }
        slave.channel = CHANNEL; // pick a channel
        slave.encrypt = 0; // no encryption
        slaveFound = 1;
        break;
      }
    }
  }
  if (slaveFound) {
    Serial.println("Slave Found, processing..");
  } else {
    Serial.println("Slave Not Found, trying again.");
  }
  WiFi.scanDelete();
}


bool manageSlave() {
  if (slave.channel == CHANNEL) {
    if (DELETEBEFOREPAIR) {
      deletePeer();
    }

    Serial.print("Slave Status: ");
    const esp_now_peer_info_t *peer = &slave;
    const uint8_t *peer_addr = slave.peer_addr;
    bool exists = esp_now_is_peer_exist(peer_addr);
    if ( exists) {
      // Slave already paired.
      Serial.println("Already Paired");
      return true;
    } else {
      // Slave not paired, attempt pair
      esp_err_t addStatus = esp_now_add_peer(peer);
      if (addStatus == ESP_OK) {
        // Pair success
        Serial.println("Pair success");
        return true;
      } else {
        Serial.println("Not sure what happened");
        return false;
      }
    }
  } else {
    Serial.println("No Slave found to process"); // No slave found to process
    return false;
  }
}

void deletePeer() {
  const esp_now_peer_info_t *peer = &slave;
  const uint8_t *peer_addr = slave.peer_addr;
  esp_err_t delStatus = esp_now_del_peer(peer_addr);
}

// send data
void sendData() {
  static int cnt = 0;
  uint8_t data[2];
  data[0] = cnt;
  data[1] = 100;
  const uint8_t *peer_addr = slave.peer_addr;
  Serial.print("Sending: "); //Serial.println(data);
  esp_err_t result = esp_now_send(peer_addr, data, 2);
  cnt++;
  Serial.print("Send Status: ");
  if (result == ESP_OK) {
    Serial.println("Success");
  } else {
    Serial.println("Not sure what happened");
  }
}


void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  InitESPNow();

}

void loop() {
  if (slave.channel == CHANNEL) { 
    bool isPaired = manageSlave();
    if (isPaired) {
      sendData();
    } else {
      Serial.println("Slave pair failed!");
    }
  }
  else {
    ScanForSlave();
  }
  delay(20);
}

```




###関連記事
[ESP-NOW User Guide](https://www.espressif.com/sites/default/files/documentation/esp-now_user_guide_en.pdf)
[arduino-esp32のESP-NOWサンプル](https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/ESPNow/Basic)
http://www.esploradores.com/practica-6-conexion-esp-now/

-----------
aNo研：https://anoken.jimdo.com/