ESP32で計測したセンサーデータをUDPブロードキャストする

ESP32にはさまざまなセンサーを接続できますが、それをUDPブロードキャストして、スマホで受信します。

今回は、ESP32で計測したセンサーデータをUDPブロードキャストところまでです。
AndroidやiOSのスマホでUDPブロードキャストを受信するところは次回投稿します。

ESP32あるいはM5Stack用のセンサーはたくさんありすぎますが、今回は環境光(明るさ)と赤外線リモコン信号を取り上げます。
取り上げた理由は以下の通りです。

* 環境光
Androidではスマホに付属のセンサを使えますが、iOSではアプリから環境光の値を取得できません。

* 赤外線リモコン信号
市販のマルチリモコンのボタン押下を何らかのトリガとして利用したいためです。

全体構成はこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/1b5c1978-8365-cd71-1348-bf5a905650d0.png)

今回は、この2つを取り上げましたが、別のセンサを扱うように修正するのは容易かと思います。
ついでに、ESP32を常時電源入れておくので、LINEビーコンにもしておきます。

ソースコードもろもろは、GitHubに上げておきました。

LineBeaconSensor

https://github.com/poruruba/LineBeaconSensor

以下のセンサユニットを使っています。

M5Stack用光センサユニット [U021]

https://www.switch-science.com/products/4051

M5Stack用赤外線送受信ユニット [U002]

https://www.switch-science.com/products/5699

ESP32として、M5StickCを使いました。もちろん、ESP32であればなんでも大丈夫です。
　M5StickCについているボタン押下もUDPブロードキャストするようにしました。

M5StickC--販売終了

https://www.switch-science.com/products/5517

# WiFi接続

WiFi接続処理をライブラリ化しておきました。
以下を呼び出すのみです。

```cpp:arduino/LineBeaconSensor/src/main.cpp
#include "wifi_utils.h"

wifi_try_connect(true);
```

# LINEビーコン化

LINEビーコン化は、以前ライブラリ化しました。
以下を呼び出すのみです。

```cpp:arduino/LineBeaconSensor/src/main.cpp
#include "lib_linebeacon.h"

uint8_t linebeacon_hwid[5] = { 0x01, 0x02, 0x03, 0x04, 0x05 };

  ret = linebeacon_initialize(linebeacon_hwid);
  if( ret != 0 ){
    Serial.printf("Error: linebeacon_initialize\n");
    while(1);
  }
  ret = linebeacon_start();
  if( ret != 0 ){
    Serial.printf("Error: linebeacon_start\n");
    while(1);
  }
```

# 光センサの計測

今回採用した光センサは、PINからのアナログ入力値がそのまま光の強さになります。
なので、以下の処理だけです。

```cpp:arduino/LineBeaconSensor/src/main.cpp
#define PIN_BRIGHTNESS 36

pinMode(PIN_BRIGHTNESS, ANALOG);

  uint16_t brightness = analogRead(PIN_BRIGHTNESS);
  Serial.printf("brightness=%d\n", brightness);
```

# 赤外線リモコンの受信

以下のライブラリを使わせていただきました。

IRremoteESP8266

https://github.com/crankyoldgit/IRremoteESP8266

使いやすいように、さらにライブラリ化しました。

```cpp:arduino/LineBeaconSensor/src/lib_ir.cpp
#include <IRremoteESP8266.h>
#include <IRrecv.h>
#include <IRutils.h>

static IRrecv *g_irrecv = NULL;

long ir_initialize(uint16_t recvpin, uint16_t sendpin)
{
  g_irsend = new IRsend(sendpin);
  g_irrecv = new IRrecv(recvpin);

  g_irsend->begin();
  g_irrecv->enableIRIn();

  return 0;
}

long ir_receive(decode_results *p_results){
  if (g_irrecv->decode(p_results)){
    Serial.printf("received: decode_type=%d\n", p_results->decode_type);
    if( p_results->decode_type == decode_type_t::NEC && !p_results->repeat){
      g_irrecv->resume();
      return 0;
    }
    g_irrecv->resume();
  }

  return -1;
}
```

以下のように使います。

```cpp:arduino/LineBeaconSensor/src/main.cpp
#include "lib_ir.h"

decode_results results;

  ir_initialize(PIN_IR_RECV, PIN_IR_SEND);

  ret = ir_receive(&results);
  if( ret == 0 ){
    // リモコン信号の受信後の処理
    // 受信データ：results.value
  }
```

# JSON文字列の生成

UDPでブロードキャストする電文は、次回の投稿で説明するJavascriptで扱いやすいようにJSON文字列にしました。
JSON文字列の操作には、以下のライブラリを使わせていただきました。

ArduinoJson

https://github.com/bblanchon/ArduinoJson

以下のようにJSONデータを生成して、

```cpp:arduino/LineBeaconSensor/src/main.cpp
  jsonDoc.clear();
  jsonDoc["type"] = "sensor";
  jsonDoc["brightness"] = brightness;
```

以下の関数で、文字列化しています。

```cpp:arduino/LineBeaconSensor/src/main.cpp
char *payload_make(JsonDocument& jsonDoc){
  int size = measureJson(jsonDoc);
  char *p_data = (char*)malloc(size + 1);
  if( p_data == NULL ){
    Serial.printf("malloc error");
    return NULL;
  }
  int size2 = serializeJson(jsonDoc, p_data, size);
  if( size2 <= 0 ){
    Serial.printf("serializeJson error");
    free(p_data);
    return NULL;
  }
  p_data[size2] = '\0';

  return p_data;
}
```

生成した文字列を使い終わったら、解放します。

```cpp:arduino/LineBeaconSensor/src/main.cpp
void payload_free(char *p_data){
  free(p_data);
}
```

# UDPブロードキャスト送信

UDP送信には、ライブラリAsyncUDPを使わせていただきました。
ESP32に標準でついています。

以下を参考にさせていただきました。
ありがとうございました。

[ESP32/ArduinoのUDP通信メモ (AsyncUDP)](https://qiita.com/yomori/items/3e02bec35ad1262b7b94)

ライブラリ化しておきました。

```cpp:arduino/LineBeaconSensor/src/lib_udp.cpp
#include <AsyncUDP.h>

static AsyncUDP udp;

long udp_broadcast(const char *p_message, const uint16_t port)
{
  udp.broadcastTo((uint8_t*)p_message, strlen(p_message), port);

  return 0;
}
```

以下のようにして呼ぶだけです。

```cpp:arduino/LineBeaconSensor/src/main.cpp
      ret = udp_broadcast(p_data, SEND_UDP_PORT);
```

# UDPパケット

UDPパケットは以下のようなJSON文字列となっています。

```
{"type":"sensor","brightness":4095}
{"type":"button","isPressed":1,"wasPressed":1}
{"type":"ir","decode_type":3,"value_high":0,"value_low":50167935}
```


# ソースコード

まとめるとこんな感じです。

```cpp:arduino/LineBeaconSensor/src/main.cpp
#include <M5StickC.h>
#include <ArduinoJson.h>
#include "wifi_utils.h"
#include "lib_linebeacon.h"
#include "lib_udp.h"
#include "lib_ir.h"

#define PIN_IR_SEND 32
#define PIN_IR_RECV 33
#define PIN_BRIGHTNESS 36

uint8_t linebeacon_hwid[5] = { 0x01, 0x02, 0x03, 0x04, 0x05 };

#define SEND_UDP_PORT 12346
#define LOOP_INTERVAL   5000

#define JSON_CAPACITY 1024
StaticJsonDocument<JSON_CAPACITY> jsonDoc;
decode_results results;

char *payload_make(JsonDocument& jsonDoc);
void payload_free(char *p_data);

void setup() {
  // put your setup code here, to run once:
  long ret;
  M5.begin(true, true, true);
  Serial.println("setup start");

  wifi_try_connect(true);

  ret = linebeacon_initialize(linebeacon_hwid);
  if( ret != 0 ){
    Serial.printf("Error: linebeacon_initialize\n");
    while(1);
  }
  ret = linebeacon_start();
  if( ret != 0 ){
    Serial.printf("Error: linebeacon_start\n");
    while(1);
  }

  pinMode(PIN_BRIGHTNESS, ANALOG);
  ir_initialize(PIN_IR_RECV, PIN_IR_SEND);

  Serial.println("setup end");
}

void loop() {
  // put your main code here, to run repeatedly:
  long ret;
  M5.update();

  ret = ir_receive(&results);
  if( ret == 0 ){
    jsonDoc.clear();
    jsonDoc["type"] = "ir";
    jsonDoc["decode_type"] = results.decode_type;
    jsonDoc["value_high"] = (uint32_t)((results.value >> 32) & 0xffffffff);
    jsonDoc["value_low"] = (uint32_t)((results.value >> 0) & 0xffffffff);

    char *p_data = payload_make(jsonDoc);
    if( p_data != NULL ){
      Serial.println("udp_print");
      ret = udp_broadcast(p_data, SEND_UDP_PORT);
      Serial.printf("ret=%d\n", ret);

      payload_free(p_data);
      p_data = NULL;
    }
  }

  static uint8_t btn_isPressed_prev = 0x00;
  uint8_t btn_isPressed = 0x00;
  uint8_t btn_wasPressed = 0x00; 
  if( M5.BtnA.wasPressed() ){
    Serial.println("BtnA wasPressed");
    btn_wasPressed |= 0x01;
  }
  if( M5.BtnA.isPressed() ){
    btn_isPressed |= 0x01;
  }

  if( btn_wasPressed || (btn_isPressed != btn_isPressed_prev) ){
    jsonDoc.clear();
    jsonDoc["type"] = "button";
    jsonDoc["isPressed"] = btn_isPressed;
    jsonDoc["wasPressed"] = btn_wasPressed;

    char *p_data = payload_make(jsonDoc);
    if( p_data != NULL ){
      Serial.println("udp_print");
      ret = udp_broadcast(p_data, SEND_UDP_PORT);
      Serial.printf("ret=%d\n", ret);

      payload_free(p_data);
      p_data = NULL;
    }

    btn_isPressed_prev = btn_isPressed;
  }

  static unsigned long start_tim = millis();
  unsigned long end_tim = millis();
  if( (end_tim - start_tim) < LOOP_INTERVAL )
    return;
  start_tim = end_tim;

  uint16_t brightness = analogRead(PIN_BRIGHTNESS);
  Serial.printf("brightness=%d\n", brightness);

  jsonDoc.clear();
  jsonDoc["type"] = "sensor";
  jsonDoc["brightness"] = brightness;

  char *p_data = payload_make(jsonDoc);
  if( p_data != NULL ){
    Serial.println("udp_print");
    ret = udp_broadcast(p_data, SEND_UDP_PORT);
    Serial.printf("ret=%d\n", ret);

    payload_free(p_data);
    p_data = NULL;
  }

  delay(1);
}

char *payload_make(JsonDocument& jsonDoc){
  int size = measureJson(jsonDoc);
  char *p_data = (char*)malloc(size + 1);
  if( p_data == NULL ){
    Serial.printf("malloc error");
    return NULL;
  }
  int size2 = serializeJson(jsonDoc, p_data, size);
  if( size2 <= 0 ){
    Serial.printf("serializeJson error");
    free(p_data);
    return NULL;
  }
  p_data[size2] = '\0';

  return p_data;
}

void payload_free(char *p_data){
  free(p_data);
}
```

以下の部分を各自の環境に合わせて変更してください。

```cpp:arduino/LineBeaconSensor/src/main.cpp
#define PIN_IR_SEND 32
#define PIN_IR_RECV 33
#define PIN_BRIGHTNESS 36

uint8_t linebeacon_hwid[5] = { 0x01, 0x02, 0x03, 0x04, 0x05 };

#define SEND_UDP_PORT 12346
#define LOOP_INTERVAL   5000
```

# その他

次回の投稿はこちら

　[CordovaでUDPブロードキャストを受信する](https://qiita.com/poruruba/items/3fe905f0aa36b661f1b3)

以上