5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

M5Stamp C3でLINE BeaconやiBeacon

Last updated at Posted at 2022-07-03

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

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

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

①LINE Beaconにする

以下の手順で進めます。

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

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

LINE Developerコンソール

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

以下で発行できます。

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

3.ESP32をLINE Beaconにする

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

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()で以下を呼び出すだけです。

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を指定します。

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

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

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

サーバはNode.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();
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です。

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

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

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

②iBeaconにする

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

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

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()で以下を呼ぶだけです。

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+

③赤外線受信機にする

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

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

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

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

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を使うかは以下で設定します。

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

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

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ユニットを使います。

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

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番号は以下で指定します。

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: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を以下に示します。

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に対応させるために、以下の部分が重要です。

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

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

参考

LINEボットを立ち上げるまで。LINEビーコンも。
M5Stackにせっかく赤外線がついているんだから、互いに送受信し合おう

その後

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

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

以上

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?