iBeacon をスマホで受信するテストしているときに、iBeacon 自体がないので困ることはありませんか?いや、困ってなくても作ってみるのがお勧めです。
たまに作ろうと思うと、ググってもうまく引っ掛からない(iBeaconを受信するツールはあるのですが、発信するほうはあまりないので)、自分のブログに残しておきます。
platformio.ini
初代の m5stack の platformio,ini は次の通り。m5stick cplus とかを使う場合は適宜かえておきます。
[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
monitor_speed = 115200
lib_deps =
m5stack/M5Stack@^0.3.9
RTOS を使わない場合
RTOS を使わずに framework の android で setup/loop 関数を使って作ります。iBeacon を発信するツールとして使いたいときはこれで十分です。
#include <M5Stack.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <M5Stack.h>
static BLEServer *pServer ;
static BLEAdvertising *advertising ;
#define BEACON_UUID "12345678-1111-2222-3333-56789abcdef0"
#define BEACON_MAJOR 0x0001
#define BEACON_MINOR 0x0001
#define BEACON_POWER 0xC5 // Measured power (at 1 meter distance)
void init_beacon() {
BLEAdvertisementData ibeaconData;
// 12345678-1111-2222-3333-56789abcdef0
char UUID[] = {
0x12, 0x34, 0x56, 0x78,
0x11, 0x11,
0x22, 0x22,
0x33, 0x33,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
0x00};
int major = BEACON_MAJOR ;
int minor = BEACON_MINOR ;
int txPower = BEACON_POWER;
ibeaconData.setFlags(0x1A);
std::string strServiceData = "";
strServiceData += (char)0x4c; // apple
strServiceData += (char)0x00; // apple
strServiceData += (char)0x02; // apple
strServiceData += (char)0x15; // apple
strServiceData += UUID ;
strServiceData += (char)(major >> 8); // major
strServiceData += (char)(major & 0xFF); // major
strServiceData += (char)(minor >> 8); // minor
strServiceData += (char)(minor & 0xFF); // minor
strServiceData += (char)txPower; //
ibeaconData.setManufacturerData(strServiceData);
// アドバタイズの設定
advertising = BLEDevice::getAdvertising();
advertising->setAdvertisementData(ibeaconData);
advertising->setScanResponse(false);
}
// Arduinoのsetup関数
void setup() {
// M5Stack の初期化
M5.begin();
Serial.begin(115200);
BLEDevice::init("M5Stack");
BLEServer *pServer = BLEDevice::createServer();
init_beacon();
// taskBeacon 内で start している
// 実は start 1回だけではうまくいかないことが多く、
// start/stop を繰り返す必要がある
// advertising->start();
}
// Arduinoのloop関数
void loop() {
// メインタスクのループ
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10, 10);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.print("Hello, M5Stack with FreeRTOS! ");
M5.Lcd.print(BEACON_UUID);
Serial.println("Hello, M5Stack with FreeRTOS! ");
// vTaskDelay(pdMS_TO_TICKS(3000)); // 3秒待機
advertising->start();
delay(3000);
advertising->stop();
delay(3000);
}
iBeacon で送信するサービスUUIDは、Apple の 0x004c となっていて、内部データとして iBeacon として送信したい UUID(12345678-1111-2222-3333-56789abcdef0)を設定しています。本来ならば、BEACON_UUID から 16 bytes のデータを作るところでが、面倒なので UUID 配列に詰め込んでいます。ちなみに 0x1502 は特性ID(キャラクタリスティック)のようなもので固定値です。
このコードでは、init_beacon 関数内でビーコンの発信設定をしているので、advertising->start(); を 1回だけ呼び出せばビーコンが送信されるような気もするのですが、うまくいきません。
これが BLEAdvertising クラスの仕様なのか、M5Stack の制限なのか(あるいは手元の M5Stack が不調なのか)わかりませんが、loop 関数にあるように、ある程度の間隔で start/stop を繰り返してやらないと iBeacon のデータが飛びません。
iBeacon が無事飛んでいると、次のように別のツールで動作確認ができます。
これは dotnet で作ったツールで、こんなコードで動かしています。
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Storage.Streams;
// BLEのスキャナ
BluetoothLEAdvertisementWatcher watcher;
// MACアドレスの保持(ランダムなので意味はない)
List<ulong> maclist = new List<ulong>();
Main(args);
void Main(string[] args)
{
Console.WriteLine("Folkbears iBeaconCheck");
watcher = new BluetoothLEAdvertisementWatcher()
{
ScanningMode = BluetoothLEScanningMode.Passive
};
// スキャンしたときのコールバックを設定
watcher.Received += Watcher_Received;
// スキャン開始
watcher.Start();
// キーが押されるまで待つ
Console.WriteLine("Press any key to continue");
Console.ReadLine();
}
void Watcher_Received(
BluetoothLEAdvertisementWatcher sender,
BluetoothLEAdvertisementReceivedEventArgs args)
{
var uuids = args.Advertisement.ServiceUuids;
var mac = string.Join(":",
BitConverter.GetBytes(args.BluetoothAddress).Reverse()
.Select(b => b.ToString("X2"))).Substring(6);
var name = args.Advertisement.LocalName;
var rssi = args.RawSignalStrengthInDBm;
var time = args.Timestamp.ToString("yyyy/MM/dd HH:mm:ss.fff");
if ( args.Advertisement.ManufacturerData.Count > 0)
{
var data = args.Advertisement.ManufacturerData[0];
if ( data.CompanyId == 0x004c && data.Data.Length >= 23)
{
byte[] ibeacon = new byte[data.Data.Length];
DataReader.FromBuffer(data.Data).ReadBytes(ibeacon);
if (ibeacon[0] == 0x02 && ibeacon[1] == 0x15)
{
byte[] uuid = ibeacon[2..18];
byte[] major = ibeacon[18..20];
byte[] minor = ibeacon[20..22];
byte txpower = ibeacon[22];
int majorvalue = major[0] * 256 + major[1];
int minorvalue = minor[0] * 256 + minor[1];
Console.WriteLine($"{time} [{tohex(uuid)}] {rssi} dBm {mac} "
+ string.Format("{0:x04}", majorvalue) + " "
+ string.Format("{0:x04}", minorvalue) + " "
);
}
}
}
string tohex( byte[] data )
{
return BitConverter.ToString(data).Replace("-", "").ToLower();
}
}
*.csproj ファイルはこんな感じです。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
RTOS を使う場合
先のコードでは iBeacon の発信と停止を loop 内で制御しましたが、RTOS を使えばタスク内で記述ができます。
#include <M5Stack.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <M5Stack.h>
static BLEServer *pServer ;
static BLEAdvertising *advertising ;
// タスクの定義
void task1(void *pvParameters) {
while (1) {
Serial.println("Hello from Task 1");
vTaskDelay(pdMS_TO_TICKS(1000)); // 1秒待機
}
}
void task2(void *pvParameters) {
while (1) {
Serial.println("Hello from Task 2");
vTaskDelay(pdMS_TO_TICKS(2000)); // 2秒待機
}
}
void taskBeacon(void *pvParameters) {
while (1) {
Serial.println("task beacon start");
advertising->start();
vTaskDelay(pdMS_TO_TICKS(3000)); // 3秒待機
Serial.println("task beacon stop");
advertising->stop();
vTaskDelay(pdMS_TO_TICKS(10000)); // 10秒待機
}
}
#define BEACON_UUID "12345678-1111-2222-3333-56789abcdef0"
#define BEACON_MAJOR 0x0001
#define BEACON_MINOR 0x0001
#define BEACON_POWER 0xC5 // Measured power (at 1 meter distance)
void init_beacon() {
BLEAdvertisementData ibeaconData;
// 12345678-1111-2222-3333-56789abcdef0
char UUID[] = {
0x12, 0x34, 0x56, 0x78,
0x11, 0x11,
0x22, 0x22,
0x33, 0x33,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
0x00};
int major = BEACON_MAJOR ;
int minor = BEACON_MINOR ;
int txPower = BEACON_POWER;
ibeaconData.setFlags(0x1A);
std::string strServiceData = "";
strServiceData += (char)0x4c; // apple
strServiceData += (char)0x00; // apple
strServiceData += (char)0x02; // apple
strServiceData += (char)0x15; // apple
strServiceData += UUID ;
strServiceData += (char)(major >> 8); // major
strServiceData += (char)(major & 0xFF); // major
strServiceData += (char)(minor >> 8); // minor
strServiceData += (char)(minor & 0xFF); // minor
strServiceData += (char)txPower; //
ibeaconData.setManufacturerData(strServiceData);
// アドバタイズの設定
advertising = BLEDevice::getAdvertising();
advertising->setAdvertisementData(ibeaconData);
advertising->setScanResponse(false);
}
// Arduinoのsetup関数
void setup() {
// M5Stack の初期化
M5.begin();
Serial.begin(115200);
BLEDevice::init("M5Stack");
BLEServer *pServer = BLEDevice::createServer();
init_beacon();
// taskBeacon 内で start している
// 実は start 1回だけではうまくいかないことが多く、
// start/stop を繰り返す必要がある
// advertising->start();
// タスクの作成
xTaskCreate(&task1, "task1", 2048, NULL, 5, NULL);
xTaskCreate(&task2, "task2", 2048, NULL, 5, NULL);
xTaskCreate(&taskBeacon, "taskBeacon", 2048, NULL, 5, NULL);
}
// Arduinoのloop関数
void loop() {
// メインタスクのループ
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10, 10);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.print("Hello, M5Stack with FreeRTOS! ");
M5.Lcd.print(BEACON_UUID);
Serial.println("Hello, M5Stack with FreeRTOS! ");
vTaskDelay(pdMS_TO_TICKS(3000)); // 3秒待機
}
ここでは、task1, task2, taskBeacon という3つのタスクが同時に動いています。優先度が同じなので、並列で動いているのですが、優先度を変えれば iBeacon 発信のタスクだけ優先して動かすことも可能でしょう(実験までしていませんが)。
先の iBeacon の発信/停止の切り替えは loop 関数から taskBeacon 内に移動しています。
m5stackの画面はこんな感じです。