LINE Beaconをopnizでコントロールしてみる

# はじめに
LINE Beaconが気になりつつも触れていなかったので、[opniz](https://github.com/miso-develop/opniz)というIoTフレームワークでコントールできるか試してみました。

opnizでコントロールできるとどんなことができるかというと、ビーコンのstartやstopをNode.jsから任意のタイミングで切り替えたり、`device message`というビーコンから送れる任意のメッセージを動的に変更できたりします。

それができることで何がうれしいかというと今のところパッと浮かばないのですが、何かできそうなポテンシャルはそこはかとなく感じているので、選択肢のひとつとして頭の片隅においておくといいことがあるかもしれません。



# LINE Beacon実装にあたり参考にした記事
* [LINE Beaconの擬似無限プッシュでLINE公式アカウント無料枠の制限に立ち向かう話 #linedc #iotlt](https://qiita.com/n0bisuke/items/9916a52bce75f220d0ec#xiao%E3%82%92line-beacon%E5%8C%96%E3%81%99%E3%82%8B)
* [【ESP32 × LINE】たった2行でLINEビーコンが作れるライブラリをリリースしたよ](https://xor.hateblo.jp/entry/20200821/1598011200)
* [Green Beacon ESP32 Library](https://github.com/ukkz/green-beacon-esp32)

LINE Beaconの実装は上記の記事を参考に[Green Beacon](https://github.com/ukkz/green-beacon-esp32)というLINE Simple BeaconのArduinoライブラリを使用させていただきました。
少ないコードでLINE Beaconが実装できてとても便利です！作者さまありがとうございます！



# 環境情報
* デバイス：[M5Stamp Pico](https://www.switch-science.com/products/7360)
	* [M5Stack Japan Creativity Contest 2022](https://info.switch-science.com/m5stack-creativity-contest-2022)の参加賞でゲットしたやつ
	* [M5ATOM Lite](https://www.switch-science.com/products/6262)でも動作確認しています

* 開発環境
	* OS：Windows10
	* IDE：VS Code & PlatformIO
	* Node.js：v16



# ソースコード

## Node.js（opniz & LINE Bot）
Node.jsのコードはopniz Node.js SDKをGreenBeacon用に拡張して制御するコードと、LINE BeaconからのWebhookを受けるBotサーバー用のコードの2つがあります。
Botサーバーのコードはopniz Node.js SDKのコードでimportして同時に動かしています。

```json: package.json
{
	"name": "opniz-line-beacon",
	"version": "0.0.1",
	"main": "index.mjs",
	"type": "module",
	"dependencies": {
		"@line/bot-sdk": "^7.5.2",
		"express": "^4.18.2",
		"opniz": "^0.2.4"
	}
}
```

```js: index.mjs
import { Opniz } from "opniz"
import { LINEBot } from "./linebot.mjs"


const config = {
	channelAccessToken: "<Channel Access Token>", // 書き換える
	channelSecret: "<Channel Secret>", // 書き換える
}
const botPort = 3000 // 任意のポート番号でOK
const opnizPort = 3001 // 任意のポート番号でOK

const lineBot = new LINEBot({ config, port: botPort })



// GreenBeacon用opniz拡張クラス
class GreenBeacon extends Opniz.Esp32 {
	async startBeacon(message = "") {
		return Boolean(await this.exec("startBeacon", message))
	}
	async stopBeacon() {
		return Boolean(await this.exec("stopBeacon"))
	}
	async setMessage(message = "") {
		return Boolean(await this.exec("setMessage", message))
	}
}

const opniz = new GreenBeacon({ port: opnizPort })



const main = async () => {
	while (!(await opniz.connectWait({ timeout: 5000 }))) { console.log("connect..."); await opniz.sleep(100) }
	console.log("[connected]")
	
	try {
		await opniz.startBeacon("test!") // LINE Beacon開始
		// await opniz.stopBeacon() // LINE Beacon停止
		
		// 動作テストとして1秒おきにデバイスのヒープメモリサイズを取得
		for (;;) {
			console.log(await opniz.getFreeHeap())
			await opniz.sleep(1000)
		}
		
	} catch(e) {
		console.log("[error]", e.message)
		await main()
	}
}
main()
```

```js: linebot.mjs
// LINE BeaconからのWebhookを受けるBotサーバー
import express from "express"
import { Client, middleware } from "@line/bot-sdk"

export class LINEBot {
	#bot
	#app
	
	constructor({ config, port }) {
		this.#bot = new Client(config)
		
		this.#app = express()
		this.#app.post("/", middleware(config), this.#handleWebhook)
		this.#app.listen(port)
	}
	
	#handleWebhook = (req, res) => {
		const events = req.body.events
		console.log(events)
		
		events.forEach(this.handleEvent)
		res.status(200).send("OK")
	}
	
	handleEvent = (event) => {
		switch (event.type) {
			case "message": this.handleMessage(event); break
			case "beacon": this.handleBeacon(event); break
		}
	}
	
	handleMessage = (event) => {
		this.#bot.replyMessage(event.replyToken, {
			type: "text",
			text: event.message.text,
		})
	}
	
	handleBeacon = (event) => {
		const message = Buffer.from(event.beacon.dm, "hex").toString("utf-8")
		this.#bot.replyMessage(event.replyToken, {
			type: "text",
			text: message,
		})
	}
}
```

## M5Stamp Pico（PlatformIO）

```sh: ライブラリ個別インストール（PlatformIOプロジェクトルートディレクトリで実行）
$ cd lib
$ git clone https://github.com/miso-develop/opniz-arduino-esp32
$ git clone https://github.com/ukkz/green-beacon-esp32
```

```ini: platformio.ini
[env:m5stack-atom]
platform = espressif32
board = m5stack-atom
framework = arduino
lib_deps = 
	links2004/WebSockets@^2.3.7
	bblanchon/ArduinoJson@^6.19.4
monitor_speed = 115200
board_build.partitions = huge_app.csv
```

```cpp: main.cpp
#include <OpnizEsp32.h>
#include <lib/WiFiConnector.h>

const char* ssid = "<Wi-Fi SSID>"; // 書き換える
const char* password = "<Wi-Fi Password>"; // 書き換える

const char* address = "<IP Address>"; // Node.js SDKを実行するマシンのIPアドレスへ書き換える
const uint16_t port = 3001; // 任意のポート番号でOK（Node.js SDKと合わせる）

const String hwid = "<Hardware ID>"; // LINE Official Account Managerのビーコンページで取得したハードウェアIDに書き換える

WiFiConnector wifiConnector(ssid, password);
Opniz::Esp32* opniz = new Opniz::Esp32(address, port);



#include "GreenBeacon.h"
GreenBeacon beacon;

// GreenBeacon関数用ハンドラ定義
class startBeaconHandler : public BaseHandler {
public:
    String name() override { return "startBeacon"; };
    String procedure(JsonArray params) override {
        Serial.println("startBeacon");
        String message = params[0];
        Serial.println(message);
        beacon.start(message);
        return "true";
    }
};
class stopBeaconHandler : public BaseHandler {
public:
    String name() override { return "stopBeacon"; };
    String procedure(JsonArray params) override {
        Serial.println("stopBeacon");
        beacon.stop();
        return "true";
    }
};
class setMessageHandler : public BaseHandler {
public:
    String name() override { return "setMessage"; };
    String procedure(JsonArray params) override {
        Serial.println("setMessage");
        String message = params[0];
        Serial.println(message);
        beacon.setMessage(message);
        return "true";
    }
};



void setup() {
    Serial.begin(115200);
    
    wifiConnector.connect();
    
    beacon = GreenBeacon(hwid);
    
    opniz->addHandler({
        new startBeaconHandler,
        new stopBeaconHandler,
        new setMessageHandler,
    });
    opniz->connect();
}

void loop() {
    opniz->loop();
    wifiConnector.watch();
}
```



# ハマったポイントと解決策

## PlatformIOでGreenBeaconをインストールしたらバージョンが古かった
* PlatformIOでGreenBeaconをインストールしたあと、[【ESP32 × LINE】たった2行でLINEビーコンが作れるライブラリをリリースしたよ](https://xor.hateblo.jp/entry/20200821/1598011200)を参考に2行のコードを書いてみたけどエラー
	* バージョンがv0.1.0だった（最新はv0.2.0）
		* libディレクトリに[GitHubリポジトリ](https://github.com/ukkz/green-beacon-esp32)をcloneして最新版を使用して解決

## 書き込みサイズオーバー
![image](https://user-images.githubusercontent.com/22117028/208947992-4517d18d-5141-4fea-83b4-c4240b17ef2d.png)

* コンパイル後、書き込みしようとしたらサイズオーバーでエラー
	* とりあえずググってみたらパーティションサイズを変更できるっぽい
		* 参考
			* [Is there an Arduino framework for the ESP32-S module? #309](https://github.com/platformio/platform-espressif32/issues/309)
			* [Partition Tables（PlatformIO）](https://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables)
		* テンプレートがいくつか用意されてる
			* `huge_app.csv`という大容量アプリ向けのでいけそう
		* platformio.iniに`board_build.partitions = huge_app.csv`と追記して書き込みで解決

## メモリ不足
* 当初M5ATOM Liteで実装していたのですがパニックで再起動を繰り返す状況に
	* どうやらBluetoothコンポーネントが結構メモリ食うようでメモリ不足っぽい
		* 参考：[Enabling ble tracker disconnects wifi #1215](https://github.com/esphome/issues/issues/1215)
	* opniz Arduino Libraryを[M5ATOM用](https://github.com/miso-develop/opniz-arduino-m5atom)からミニマム版の[ESP32版](https://github.com/miso-develop/opniz-arduino-esp32)に変更して解決



# おわりに
ROM不足にRAM不足と普段なかなか経験しないようなマイコンならではのエラーに悩まされて楽しかったです。

無事opnizからLINE Beaconをコントロールできるようになったので、何かいい感じに使えたらまた記事にしてみたいと思います。