M5Stamp C3でLINE BeaconやiBeacon

以前、M5Stamp C3が安かったので、複数買ったのですが、なかなかPlatformIOが正式に対応してくれないので、ずっと眠っていました。
そんなところ、有志の方が、M5Stamp C3に対応してくださったので、これを機に、M5Stamp C3の用途を考えて、実際に実装してみました。
以下を実装します。

①	LINE Beaconにする
②	iBeaconにする
③	赤外線受信機にする
④	人感センサにする

ちなみに、③と④で検知した情報は、UDPで他のPCに通知するような実装としています。
もろもろのソースコードは、GitHubに上げておきました。

https://github.com/poruruba/M5StampC3_test

# ①LINE Beaconにする

以下の手順で進めます。

１.	LINE Developerコンソールで、Messaging APIのチャネルを作成する
２.	LINE BeaconのハードウェアIDを発行する
３.	ESP32をLINE Beaconにする
４.	LINEのチャネルシークレットとチャネルアクセストークン（長期）を払い出す。
５.	LINE Beacon通知を受け取るサーバを立ち上げる
６.	サーバをスマホのLINEアプリにお友達登録する

１. LINE Developerコンソールで、Messaging APIのチャネルを作成する

LINE Developerコンソール

https://developers.line.biz/console/

２.LINE BeaconのハードウェアIDを発行する

以下で発行できます。

https://manager.line.biz/beacon/register

「LINE Simple BeaconのハードウェアIDを発行」ボタンを押下したのち、いずれかのMessaging APIのチャネルを選択してください。６.ではこのチャネルとお友達になります。
ハードウェアIDとして、5バイトの16進数が払い出されます。

３.ESP32をLINE Beaconにする

LINE Beaconの仕様は以下にあります。

https://github.com/line/line-simple-beacon/blob/master/README.ja.md

```cpp:Arduino\M5Stamp_test\src\lib_linebeacon.cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>

#include "lib_linebeacon.h"

#define UUID_SERVICE_SHORT  0xFE6F
#define UUID_SERVICE BLEUUID((uint16_t)UUID_SERVICE_SHORT)

static uint8_t LINE_BEACON_HWID[5]; // LINE BEACON HWID
static BLEAdvertising *pAdvertising;
static bool is_running = false;

long linebeacon_set_device_message(const char *p_device_message, int8_t tx)
{
  uint8_t message_len = strlen(p_device_message);
  if( message_len < 1 || message_len > 13 )
    return -1;

  BLEAdvertisementData advertisementData = BLEAdvertisementData();
  advertisementData.setFlags(0x06);
  
  std::string strServiceData = "";

  strServiceData += (char)0x03;
  strServiceData += (char)0x03;
  strServiceData += (char)0x6f;
  strServiceData += (char)0xfe;

  strServiceData += (char)(10 + message_len);
  strServiceData += (char)0x16;
  strServiceData += (char)(UUID_SERVICE_SHORT & 0xff);
  strServiceData += (char)((UUID_SERVICE_SHORT >> 8) & 0xff);
  strServiceData += (char)0x02; // LINE Simple Beacon FrameのFrame Type
  strServiceData += LINE_BEACON_HWID[0]; // LINE Simple Beacon FrameのHWID(5B)
  strServiceData += LINE_BEACON_HWID[1];
  strServiceData += LINE_BEACON_HWID[2];
  strServiceData += LINE_BEACON_HWID[3];
  strServiceData += LINE_BEACON_HWID[4];
  strServiceData += (char)tx; // LINE Simple Beacon FrameのMeasured TxPower

  // LINE Simple Beacon FrameのDevice Message(1-13)
  for( int i = 0 ; i < message_len ; i++ )
    strServiceData += p_device_message[i];

  advertisementData.addData(strServiceData);
  pAdvertising->setAdvertisementData(advertisementData);

  return 0;
}

long linebeacon_initialize(const uint8_t *p_hwid, int8_t tx)
{
  BLEDevice::init(LINEBEACON_DEVICE_NAME);

  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(UUID_SERVICE);
  pService->start();

  pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(UUID_SERVICE);

  memmove(LINE_BEACON_HWID, p_hwid, sizeof(LINE_BEACON_HWID));

  return linebeacon_set_device_message(LINEBEACON_DEFAULT_DEVICE_MESSAGE, tx);
}

long linebeacon_start(void)
{
  if( !is_running ){
    is_running = true;
    pAdvertising->start();
  }

  return 0;
}

long linebeacon_stop(void)
{
  if( is_running ){
    pAdvertising->stop();
    is_running = false;
  }

  return 0;
}
```

使い方は、setup()で以下を呼び出すだけです。

```cpp:Arduino\M5Stamp_test\src\main.cpp
#include "lib_linebeacon.h"

const uint8_t LINE_BEACON_HWID[] = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };

setup(){
  linebeacon_initialize(LINE_BEACON_HWID, -56);
  linebeacon_start();
}
```

LINE_BEACON_HWIDには、払い出されたハードウェアIDを指定します。

４.LINEのチャネルシークレットとチャネルアクセストークン（長期）を払い出す。

LINE Developerコンソールから、さきほどのチャネルを選択し、チャネルシークレットとチャネルアクセストークン（長期）を払い出します。

５.LINE Beacon通知を受け取るサーバを立ち上げる

サーバはNode.jsで作成しました。
以下の部分が該当します。

```js:nodejs\api\controllers\linebeacon-api\index.js
'use strict';

const HELPER_BASE = process.env.HELPER_BASE || "/opt/";

const config = {
  channelAccessToken: '[LINEのチャネルアクセストークン]',
  channelSecret: '[LINEのチャネルシークレット]',
};

const LineUtils = require(HELPER_BASE + 'line-utils');
const line = require('@line/bot-sdk');
const app = new LineUtils(line, config);

app.beacon( async (event, client) =>{
	console.log(event);

	var message = app.createSimpleResponse("ビーコン(" + event.beacon.dm + ")の受信圏内に入りました");
	return client.replyMessage(event.replyToken, message);
});

exports.fulfillment = app.lambda();
```

```yaml:nodejs\api\controllers\linebeacon-api\swagger.yaml
paths:
  /linebeacon-api:
    post:
      x-handler: fulfillment
      parameters:
        - in: body
          name: body
          schema:
            type: object
      responses:
        200:
          description: Success
          schema:
            type: object
```

channelAccessTokenとchannelSecretに、LINEのチャネルシークレットとチャネルアクセストークン（長期）を指定します。

立ち上げるサーバはHTTPSである必要があるため、certフォルダを作成して、そこにSSL証明書を置いてください。

　```https://[Node.jsサーバのURL]:[ポート番号]/linebeacon-api```

というエンドポイントでLINEサーバからの通信を受信します。
LINE Developerコンソールの、「Webhook URL」に上記を指定し、検証に成功すればOKです。

６.サーバをスマホのLINEアプリにお友達登録する

LINE Developerコンソールの対象チャネルにある、「QRコード」を使って、スマホのLINEアプリからスキャンすれば、お友達として登録されます。

これで、スマホが、M5Stamp C3のBLEの通信圏内に入ると「ビーコン(XXXXXXX)の受信圏内に入りました」というメッセージが届くはずです。

# ②iBeaconにする

iBeaconの仕様は以下にあります。

https://developer.apple.com/ibeacon/Getting-Started-with-iBeacon.pdf

ESP32のソースコードにするとこんな感じです。

```cpp:Arduino\M5Stamp_test\src\lib_ibeacon.cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>

#include "lib_ibeacon.h"

static BLEAdvertising *pAdvertising;
static bool is_running = false;

long ibeacon_set_device_message(const uint8_t *p_uuid, uint16_t major, uint16_t minor, int8_t tx)
{
  BLEAdvertisementData advertisementData = BLEAdvertisementData();
  advertisementData.setFlags(0x06);
  
  std::string strServiceData = "";

  strServiceData += (char)26;
  strServiceData += (char)0xff;
  strServiceData += (char)0x4c;
  strServiceData += (char)0x00;
  strServiceData += (char)0x02;
  strServiceData += (char)0x15;

  for( int i = 0 ; i < 16 ; i++ )
    strServiceData += (char)p_uuid[i];

  strServiceData += (char)((major >> 8) & 0xff);
  strServiceData += (char)((major >> 0) & 0xff);
  strServiceData += (char)((minor >> 8) & 0xff);
  strServiceData += (char)((minor >> 0) & 0xff);
  strServiceData += (char)tx; // Measured TxPower

  advertisementData.addData(strServiceData);
  pAdvertising->setAdvertisementData(advertisementData);

  return 0;
}

long ibeacon_initialize(const uint8_t *p_uuid, uint16_t major, uint16_t minor, int8_t tx)
{
  BLEDevice::init(IBEACON_DEVICE_NAME);

  BLEServer *pServer = BLEDevice::createServer();
  pAdvertising = pServer->getAdvertising();

  return ibeacon_set_device_message(p_uuid, major, minor, tx);
}

long ibeacon_start(void)
{
  if( !is_running ){
    is_running = true;
    pAdvertising->start();
  }

  return 0;
}

long ibeacon_stop(void)
{
  if( is_running ){
    pAdvertising->stop();
    is_running = false;
  }

  return 0;
}
```

使い方は、setup()で以下を呼ぶだけです。

```cpp:Arduino\M5Stamp_test\src\main.cpp
#include "lib_ibeacon.h"

uint8_t uuid[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };

setup(){
  ibeacon_initialize(uuid, 0x1234, 0x5678, -56);
  ibeacon_start();
}
```

ibeacon_initialize の3つの引数は、UUID、Major、Minorと呼ばれていて、自由に設定してください。

例えば、以下のAndroidアプリで、iBeaconを確認できます。

BeaconSET+

https://play.google.com/store/apps/details?id=com.minew.beaconplus&hl=ja&gl=US

# ③赤外線受信機にする

以下の赤外線送受信ユニットを使います。

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

しかしながら、M5Stamp C3には、Grove端子がありません。
そこで以下の変換コネクタを使います。

https://www.switch-science.com/catalog/7093/

これを、M5Stamp C3の両側の端子にぶっさします。USB端子側の両側4つがGrove端子に相当します。
ただし、ここでたいへんたいへん重要なことがあります。実際にやってみるとわかるのですが、上記の変換コネクタをぶっさすと、左右が逆になります。なので、Groveケーブルを接続するときには、表裏反対にさしましょう。逆刺し防止の突起をハサミで切る必要があります。

※これを間違うと、＋と－間違ってさすことになって、最悪M5Stamp C3やM5ユニットを壊す場合があるので、よく注意して下さいっ！

```cpp:Arduino\M5Stamp_test\src\lib_ir.cpp
#include <Arduino.h>
#include <IRsend.h>
#include <IRrecv.h>
#include <IRutils.h>

#include "lib_ir.h"

static IRsend irsend(IR_SEND_PORT);
static IRrecv irrecv(IR_RECV_PORT);
static decode_results results;

static IrReceiveCallback g_ir_receive_callback = NULL;

long ir_initialize(void)
{
  irsend.begin();
  irrecv.enableIRIn();

  return 0;
}

long ir_send(uint16_t address, uint16_t command)
{
  uint32_t value = irsend.encodeNEC(address, command);
  irsend.sendNEC(value);

  return 0;
}

long ir_send_value(uint32_t value)
{
  irsend.sendNEC(value);

  return 0;
}

void ir_set_callback(IrReceiveCallback callback)
{
  g_ir_receive_callback = callback;
}

void ir_receive_update(void)
{
  if (irrecv.decode(&results)) {
    if(results.overflow){
//      Serial.println("Overflow");
      irrecv.resume(); 
      return;
    }
    if( results.decode_type != decode_type_t::NEC || results.repeat ){
//      Serial.println("not supported");
      irrecv.resume(); 
      return;
    }

    if( g_ir_receive_callback != NULL )
      g_ir_receive_callback(results.address, results.command);

    irrecv.resume(); 
  }
}
```

どのPINを使うかは以下で設定します。

```c:Arduino\M5Stamp_test\src\lib_ir.h
#ifndef _LIB_IR_H_
#define _LIB_IR_H_

#include <Arduino.h>

#if defined(ARDUINO_M5Stick_C)
#define IR_SEND_PORT 32
#define IR_RECV_PORT 33
#elif defined(ARDUINO_M5Stack_ATOM)
#define IR_SEND_PORT 26
#define IR_RECV_PORT 32
#elif defined(ARDUINO_ESP32C3_DEV)
#define IR_SEND_PORT 1
#define IR_RECV_PORT 0
#endif

typedef long(*IrReceiveCallback)(uint16_t address, uint16_t command);

long ir_initialize(void);
long ir_send(uint16_t address, uint16_t command);
long ir_send_value(uint32_t value);
void ir_set_callback(IrReceiveCallback callback);
void ir_receive_update(void);

#endif
```

使い方は以下の通りです。

```cpp:Arduino\M5Stamp_test\src\main.cpp
#include "lib_ir.h"

long ir_callback(uint16_t address, uint16_t command)
{
  Serial.printf("address=%d command=%d\n", address, command);
  return 0;
}

setup(){
  ir_initialize();
  ir_set_callback(ir_callback);
}

loop(){
ir_receive_update();
}
```

# ④人感センサにする

以下のM5ユニットを使います。

https://www.switch-science.com/catalog/5697/

単にGPIOをdigitalReadするだけなので、簡単ですね。

```cpp:M5Stamp_test\src\lib_pir.cpp
#include <Arduino.h>
#include "lib_pir.h"

static PirDetectCallback g_callback;
static int last_pir = 0;

void pir_initialize(void){
  pinMode(PIR_PORT, INPUT_PULLUP);
}

void pir_set_callback(PirDetectCallback callback)
{
  g_callback = callback;
}

void pir_update(void)
{
  int pir = digitalRead(PIR_PORT);

  if( pir != last_pir ){
    if( g_callback != NULL )
      g_callback( pir ? true : false);
    last_pir = pir;
  }
}
```

PIN番号は以下で指定します。

```cpp:M5Stamp_test\src\lib_pir.h
#ifndef _LIB_PIR_H_
#define _LIB_PIR_H_

#include <Arduino.h>

#if defined(ARDUINO_M5Stick_C)
#define PIR_PORT  36
#elif defined(ARDUINO_M5Stack_ATOM)
#define PIR_PORT  21
#elif defined(ARDUINO_ESP32C3_DEV)
#define PIR_PORT  18
#endif

typedef long(*PirDetectCallback)(bool);

void pir_initialize(void);
void pir_set_callback(PirDetectCallback callback);
void pir_update(void);

#endif
```

使い方は以下の通りです。

```cpp:cpp:M5Stamp_test\src\main.cpp
#include "lib_pir.h"

long pir_callback(bool detected)
{
  Serial.printf("detect=%d\n", detected);
  return 0;
}

setup(){
  pir_initialize();
  pir_set_callback(pir_callback);
}

loop(){
  pir_update();
}
```

# M5Stamp C3対応について

Arduinoソースコードは、Visual Studio CodeのPlatformIOで作成しています。
M5Stamp C3用と言いましたが、実際には、M5StickCやM5Atomなどでも動作します。

Visual Studio Codeの画面の下にある「Switch PlatformIO Project Environment」から、対象のESP32のCPUを選択してください。
（env:esp32c3を最初に選択したときは、かなり時間がかかりますので、辛抱強く待ちましょう）

platformio.iniを以下に示します。

```ini:Arduino\M5Stamp_test\platformio.ini
; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:m5stack-atom]
platform = espressif32
board = m5stack-atom
framework = arduino
monitor_speed = 115200
upload_port = COM4
monitor_port = COM4
board_build.partitions = no_ota.csv
lib_deps = 
	m5stack/M5Atom@^0.0.9
	fastled/FastLED@^3.5.0
	adafruit/Adafruit NeoPixel@^1.10.3
	bblanchon/ArduinoJson@^6.19.4
	crankyoldgit/IRremoteESP8266@^2.8.2

[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
monitor_speed = 115200
upload_port = COM4
monitor_port = COM4
board_build.partitions = no_ota.csv
lib_deps = 
	m5stack/M5StickC@^0.2.5
	adafruit/Adafruit NeoPixel@^1.10.3
	bblanchon/ArduinoJson@^6.19.4
	crankyoldgit/IRremoteESP8266@^2.8.2

[env:esp32c3]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_port = COM5
monitor_port = COM5
board_build.partitions = no_ota.csv
board_build.mcu = esp32c3
board_build.variant = esp32c3
board_build.f_cpu = 160000000L
board_build.f_flash = 80000000L
board_build.flash_mode = dio
board_build.arduino.ldscript = esp32c3_out.ld
build_unflags = 
	-DARDUINO_ESP32_DEV
build_flags = 
	-DARDUINO_ESP32C3_DEV
	-DCORE_DEBUG_LEVEL=0
lib_deps = 
	adafruit/Adafruit NeoPixel@^1.10.3
	bblanchon/ArduinoJson@^6.19.4
	crankyoldgit/IRremoteESP8266@^2.8.2
```

上述の通り、M5Stamp C3に対応させるために、以下の部分が重要です。

```ini:Arduino\M5Stamp_test\platformio.ini
board_build.mcu = esp32c3
board_build.variant = esp32c3
board_build.f_cpu = 160000000L
board_build.f_flash = 80000000L
board_build.flash_mode = dio
board_build.arduino.ldscript = esp32c3_out.ld
build_unflags = 
	-DARDUINO_ESP32_DEV
build_flags = 
	-DARDUINO_ESP32C3_DEV
	-DCORE_DEBUG_LEVEL=0
```

以下を参考にさせていただきました。

https://github.com/leCandas/M5Stamp-C3

# 参考

[LINEボットを立ち上げるまで。LINEビーコンも。](https://qiita.com/poruruba/items/c71c96ca83c7403c2930)
[M5Stackにせっかく赤外線がついているんだから、互いに送受信し合おう](https://qiita.com/poruruba/items/60fe119ef20e3cd38e2e)

# その後

よく見たら、以下でOKでした。。。

```ini:platformio.ini
[env:esp32-c3-devkitm-1]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
upload_port = COM5
monitor_port = COM5
board_build.partitions = no_ota.csv
build_flags = -DCORE_DEBUG_LEVEL=0
lib_deps = 
	adafruit/Adafruit NeoPixel@^1.10.3
	bblanchon/ArduinoJson@^6.19.4
	crankyoldgit/IRremoteESP8266@^2.8.2
```


以上