【FUN Advent Calender 2020】こんな世の中なんで、ハードウェアでもいじって""""自己防衛"""していこう
前置き
前回はSyuya Abeさんの 「オンライン夏学運営スタッフの振り返り」でした!
はじめに
Hello world from REAL WORLD !!!
どうもこんにちわ、Yuzuka4573です
情報デザインコースでデザインをせず、3Dプリンタ、レーザーカッター、基盤加工機を酷使して、4年間VR関連の研究室で、色々ソフトやハードを作っていました。
ですが、残念ながら無事9月に卒業してしまいました。。。
しかしながら、知ってる人は知っている通り、
ま だ 、 就 職 で き て な い ん で す よ ね . . .
うん、某ウイルス君のせいだね!
お前がクソ人材すぎるから誰も雇いたくないんだよ
それはさておき、9月以降は何もする予定がなかったということで、某OS研の教授様からの依頼を頂きました。
その依頼の中でハードウェア(以後HW)の選定とか回路設計、中のファームウェアの制作などを担当しています。
HW製作中に色々面白い知見を得たので、誰かの助けになればいいなと思います。
どんな要件?
単純に言えば「iBeaconを送信したり、受信できる装置を作る」というお仕事です。
何をつかうか、何を情報として送信するか、何に記録するか…ある程度すべて決めるというなかなか裁量範囲が広いお仕事です。
ただし、こっちはもう卒業済みの人間。現役の時のように、大学の設備などを無限に使うことはできません。なので、今流行りのリモートワークである程度乗り切ろうという感じになりました。
何使ったの?
今回の記事ではこんなものを使いました
- Autodesk EAGLE (回路設計ソフト)
- ESP32 (マイコンボード)
- 適当なRTC(DS3231)
- 適当なMicroSDカードの基盤
- Bluetooth LE Sniffer
- M5StickC (Plus)
などなど…
なんだかんだで、地味にお金かかってます(色々出してもらって圧倒的感謝)
ちなみに知ってる人もいるかも知れないけど一応解説
ESP32
大体のHW大好き人間が知っているであろう(当人調べ)素敵なマイコンボード
ブレッドボードに刺さるタイプのDevKitでも1500円ぐらいで購入できるすごいやつ
(ちなみに、学部1年で触る、デザインコース生御用達のArduinoUnoくんは3300円するし、ない機能がある)
こいつは本来ESP-IDFなるものでプログラムしなきゃいけないけど、作った会社がlibraryを提供してくれるおかげで、ArduinoIDEでも開発が可能!
しかも、2.4GHzのWifiにも接続できるし、Bluetooth4.0も使える素敵仕様。最近のIoT自作勢はこれで開発してると言っても過言でもない
回路がある程度わかるプロにはESP32のコアな部分(というか本体)だけであるESP32-WROOM-32Dをおすすめする
M5StickC
M5シリーズはたくさんあるが、大きさの関係でとりあえずM5StickC (Plus)を選択
中には80mAhのバッテリ、0.96inchの液晶、6軸IMUなど、ESP32DevKitよりも色々なモジュールが搭載されている
そのため、植物の自動水やり装置から、モーショントラッキングデバイスまで割と何でも作れるモジュールである
その他センサは、ポートや形状の規格に合わせれば、結構きれいに収まるのでそれも良いポイント
Bluetooth LE Sniffer
正式名称はnRF51822搭載 Bluefruit LE Sniffer
なんかこの世に飛んでるBLEシグナルを傍受できるすごいやつ(適当)
仕様には通信解析でおなじみのWireSharkと専用プラグインが必要
ちなみにこれは、知る人ぞ知る、通称緑のヤクザの置き土産(らしい)(これがあってすごく助かった、アリガトウ)
iBeaconについて
そういえば、iBeacon1って知っていますか?
こいつはBluetooth Low Energy ビーコンの一種で、省電力、低コストの通信プロトコルを有しているAppleが作ったものです。
使い方の例として、スマートフォンが自分の位置情報を得るために使ったり、店に入ったら自動でポイントを付与させたりできるってやつです。
iOSではver.7、Androidではver4.3から利用することができるようになっています。
ところで、「低コストの通信プロトコル」とのことですが、なんぞやって話です
それがこちら…
これをAdvertising Dataと言われる領域にこれらのデータを突っ込みます。
まぁまぁシンプルですね…
ココで重要なのは、設定すべきものがProximity UUID, Major, Minor, Measured Powerの4項目ぐらいしかないってことです。ワーカンタン!
変な話、「ビーコンの大まかなサービスの名前」,「識別番号1」,「識別番号2」,「電波強度」って思うといいかもです。
iBeaconの利用霊的には同じサービス名のビーコンがたくさんあって、識別番号1で大まかな場所とか建物の階数を識別する番号、識別番号2で細かい場所のIDを振ってあげるといい感じに使えるってイメージ。
電波強度に関しては、別な場所で違うIDが出てこない程度に強度調整すればいいかなって考えてます。
ちなみに、自作iBeaconを作るときは、AppleのiBeaconドキュメントに沿って作らないと、iOSで検知できない場合もあるので注意です!
早速自作iBeacon作成、なにもないわけではなく…
前置きはココまでで、本編です。
今回はESP32 DevKitを使ってiBeaconの送信機、受信機を作ります!
ArduinoIDEで開発環境を作って、早速実装!
受信機を作る
終
━━━━━━
制作・著作
Ⓨⓤⓩⓤⓚⓐ
冗談でなく、これだけで検知できます。
これをベースに使っています。
いろんな情報を取得するためにこんな改変を施しています。
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
char uuid[36];
BLEAddress addr = advertisedDevice.getAddress();
int rssi = advertisedDevice.getRSSI();
// get ManufacturerData
std::string data = advertisedDevice.getManufacturerData();
Serial.println("BLE status");
Serial.print("conpany : ");Serial.println(String((data[0]<<8)+data[1],HEX));
Serial.print("type : ");Serial.println(String((data[2]<<8)+data[3],HEX));
Serial.print("length : ");Serial.println(String(data.length()));
Serial.print("rssi : ");Serial.println(String(rssi));
Serial.print("addr : ");Serial.println(addr.toString().c_str());
int txp = advertisedDevice.getTXPower();
Serial.print("TXP : ");Serial.println(String(txp));
Serial.print("Distance : ");Serial.println(10^((txp - rssi)/20));
Serial.println("/////DUMP/////");
for(int c = 0;c<data.length();c++){
Serial.print(data[c],HEX);
Serial.print(" ");
}
Serial.println("\r\n");
if(data.length() > 23)
{
// check company ID and Beacon type
if((data[0] == 0x4c) && (data[1] == 0x00) && (data[2] == 0x02) && (data[3] == 0x15)) {
// UUID fix
sprintf(uuid,"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X"
,data[4],data[5],data[6],data[7],data[8],data[9],data[10],data[11],data[12],data[13]
,data[14],data[15],data[16],data[17],data[18],data[19]);
int major = (int)(((data[20]&0xff) << 8) + (data[21] & 0xff));
int minor = (int)(((data[22]&0xff) << 8) + (data[23] & 0xff));
signed char power = (signed char)(data[24]&0xff);
Serial.printf("addr=%s rssi=%d uuid=%s,major=%d,minor=%d,power=%d\n",addr.toString().c_str(),rssi,uuid,major,minor,power);
}
}
}
};
問題点があるとすれば、ありとあらゆるBLEを検知することとProximity UUIDが逆転する現象が発生することですね。
BLEのふるい分けに関しては、iBeaconの仕様を利用します。
先程のiBeaconの通信プロトコルで、固定されているものがいくつかありますが、CompanyID2とADTypeで認識します。
iBeaconはConpanyIDと0x4cとなります。これはApple inc.を指しています。またBeaconTypeが 0x0215となります。
if((data[0] == 0x4c) && (data[1] == 0x00) && (data[2] == 0x02) && (data[3] == 0x15)) {
// Your processing here
}
また、Proximity UUIDが逆転する現象については
sprintf(uuid,"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X"
,data[4],data[5],data[6],data[7],data[8],data[9],data[10],data[11],data[12],data[13]
,data[14],data[15],data[16],data[17],data[18],data[19]);
上記のようにすればだいたいどうにかなる(なんかスマートの方法があったら教えて下さい…)
あとは、好きにSDカードとかに書き出して任務完了です…。
鬼門 自作iBeacon送信機をつくる
さて、ここからちょっと大変です。
ESP32 BLE ArduinoのサンプルにはもちろんiBeacon送信のコード3があります。
こいつをESP32DevKitに書き込んで実行!
飛んでいるかどうかはスマホアプリのnRF Connectで確認します。
.........。
>>>できてるやんけ<<<
はい、iBeaconの送信ができてますね!一応
じゃあ先程の受信機で検知できるか試してみましょう。
02:48:01.680 -> Device name: OMVR-V190
02:48:01.680 ->
02:48:01.680 -> Found ServiceUUID: 0000180a-0000-1000-8000-00805f9b34fb
02:48:01.680 ->
02:48:01.680 -> Found another manufacturers beacon!
02:48:01.727 -> strManufacturerData: 10 [4C][0][10][6][D][1E][22][9C][CA][97]
02:48:02.712 -> Found ServiceUUID: 0000fd6f-0000-1000-8000-00805f9b34fb
02:48:02.712 ->
02:48:04.949 -> Found another manufacturers beacon!
02:48:04.949 -> strManufacturerData: 10 [4C][0][10][6][6B][1E][E7][23][6C][10]
02:48:06.588 -> Devices found: 4
02:48:06.588 -> Scan done!
なにか様子がおかしいですね…?
ここで、研究室からお借りしてるビーコンをONにして、検知されるか見てみます。
02:43:37.519 -> Found another manufacturers beacon!
02:43:37.519 -> strManufacturerData: 10 [4C][0][10][6][D][1E][22][9C][CA][97]
02:43:37.614 -> Found an iBeacon!
02:43:37.614 -> iBeacon Frame
02:43:37.614 -> ID: 004C Major: 1 Minor: 0 UUID: 00000000-0018-c080-4441-454c42445348 Power: -78
02:43:37.614 -> Device name: OMVR-V190
02:43:37.614 ->
02:43:37.614 -> Found ServiceUUID: 0000180a-0000-1000-8000-00805f9b34fb
02:43:37.614 ->
02:43:38.783 -> Found ServiceUUID: 0000fd6f-0000-1000-8000-00805f9b34fb
02:43:38.783 ->
02:43:38.783 -> Found another manufacturers beacon!
02:43:38.783 -> strManufacturerData: 10 [4C][0][10][6][6B][1E][DE][0][85][89]
02:43:41.171 -> Found another manufacturers beacon!
02:43:41.217 -> strManufacturerData: 9 [4C][0][10][5][6][18][25][E2][59]
02:43:42.486 -> Devices found: 6
02:43:42.486 -> Scan done!
あっ…。
02:43:37.614 -> Found an iBeacon!
02:43:37.614 -> iBeacon Frame
02:43:37.614 -> ID: 004C Major: 1 Minor: 0 UUID: 00000000-0018-c080-4441-454c42445348 Power: -78
ExampleがExampleしてねぇ~じゃん!
はい、そうなんです。こちらのコード、一部のスキャナーでは検知されないのです。
こいつについては、公式Repoのissueにて文句が言われています。
これでは、案件を終わらせることができない…そう思ったその時、あいつの存在を思い出しました。
_人人人人人人人人人人人_
> Bluetooth LE Sniffer <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄
なにかおかしかったらとりあえず、通信見てみようということでWiresharkを使って送られてる情報を確認します。
※セットアップ方法については割愛しますので、興味あれば検索かけて、どうぞ。
とりあえず自作iBeaconが飛ばしているものを見てみると…
0000 03 06 37 01 86 07 06 0a 01 25 35 00 00 83 10 00
0010 00 d6 be 89 8e 00 24 ea f0 85 b5 aa 8c 02 01 04
0020 1a ff 4c 00 02 15 4d 6f c8 8b be 75 66 98 da 48
0030 68 66 a3 6e c7 8e 00 00 00 11 00 67 03 49
なるほどわからん。
とりあえず、正常なiBeaconのパケットも
0000 03 06 37 01 6a 23 06 0a 01 25 40 00 00 f9 0e 00
0010 00 d6 be 89 8e 42 24 a5 d1 18 a0 e9 eb 02 01 06
0020 1a ff 4c 00 02 15 48 53 44 42 4c 45 41 44 80 c0
0030 18 00 00 00 00 00 00 01 00 00 b2 ad af 6a
なーにこれ…。
とりあえず言えることはiBeaconの情報を司るところがここになります。
自作ビーコン
0000 || 03 06 37 01 86 07 06 0a 01 25 35 00 00 83 10 00
0010 || 00 d6 be 89 8e 00 24 ea f0 85 b5 aa 8c 02 01 04
0020 || 1a ff 4c 00 02 15 4d 6f c8 8b be 75 66 98 da 48
0030 || 68 66 a3 6e c7 8e 00 00 00 11 00 67 03 49
製品のビーコン
0000 || 03 06 37 01 6a 23 06 0a 01 25 40 00 00 f9 0e 00
0010 || 00 d6 be 89 8e 42 24 a5 d1 18 a0 e9 eb 02 01 06
0020 || 1a ff 4c 00 02 15 48 53 44 42 4c 45 41 44 80 c0
0030 || 18 00 00 00 00 00 00 01 00 00 b2 ad af 6a
赤いところがだいたいの情報です。
最初の1-2byte目でCompanyID,3-4byte目でBeaconTypeですね。
そこから5-15byte目までで、Proximity UUIDです。上の例では、自作ビーコンは"4d 6f c8 8b be 75 66 98 da 48 68 66 a3 6e c7 8e"、製品のビーコンは"48 53 44 42 4c 45 41 44 80 c0 18 00 00 00 00 00"となりますね。
その後の16-17byte目はMajor、18-19byte目はMinor、最後にMeasuredPowerが20byte目に来ます。
ここまでみてみると、そんな大差はないですよね?(UUID,Major,Minorは変わることは良いとして)
問題は、このAdvertising Dataより以前の話でないかと思いました。
Advertising Dataの前にはAccessAddress, PDU Headerと呼ばれるものが存在します。
場所的には、
0000 || 03 06 37 01 6a 23 06 0a 01 25 40 00 00 f9 0e 00
0010 || 00 d6 be 89 8e 42 24 a5 d1 18 a0 e9 eb 02 01 06
0020 || 1a ff 4c 00 02 15 48 53 44 42 4c 45 41 44 80 c0
0030 || 18 00 00 00 00 00 00 01 00 00 b2 ad af 6a
緑色の部分(17-21byte目)ですね。
個々にはAccessAddressが4Byte分、PDU Headerで2Byte使用しています。
AccessAddressは0x8e89bed6で固定値4になっています。
次にPDU Headerです。PDUとは Protocol Data Unitの略で、プロトコルが扱うひとまとまりのデータ送受信単位のことです。
構造的にはこんな感じ。
ここで2つのビーコンのPDU Headerを見てみましょう。
自作iBeaconでは"00 24"で、製品のビーコンでは"42 24"となっています。
2進数に直すと " 0000 0000 0001 1000"と "0010 1010 0001 1000"
ここで重要なのは最初の8bitsです。
RFUに関しては現在使用していない未定義の領域なので無視しますが一番重要なのは、最後の4bitsのPDU Typeです。
PDU Typeには、7種類ほどあります。今回の場合では自作iBeaconではADV_IND、製品のビーコンではADV_NONCONN_INDとなっています
なので、Arduinoのコードをでどうにかします。
どうにかします…。
どうにか…。
>>>どうにもできません!<<<
というのも、現状、ArduinoIDEから入れられるバージョンでは、PDU Typeを切り替えすることができないようです。
そこで、ESP32のボードlibraryのmasterをクローンしてきて、”Instructions for Windows”の手順に沿って導入していきます。
導入後に、BLE_iBeacon.inoを開き、setBeacon関数内の”pAdvertising->setScanResponseData(oScanResponseData);”の次の行に以下を追記します
pAdvertising->setAdvertisementType(ADV_TYPE_NONCONN_IND);
適用後に自作iBeaconのパケットを確認すると以下の通りになります。(青いところ)
0000 || 03 06 37 01 5a 57 06 0a 01 25 2c 00 00 ca 1a 00
0010 || 00 d6 be 89 8e 02 24 ea f0 85 b5 aa 8c 02 01 06
0020 || 1a ff 00 4c 02 15 e8 c6 56 02 6d 9c 44 ef 97 34
0030 || b2 d3 ef 1c d9 61 00 00 1f 13 bf 3f 7d 30
いい感じに変更がなされていますね。
じゃあ、これでやっと受信機で検知できますね!
あれ…検知できてないですね…。
ココまで来ると、もう精神力がないのでissueなげました。
すると、コントリビューターの一人にこんなことを言われました。
俺の提案だけど、ディープスリープを切ればいいと思うで!
なるほど、なんでやねん。
どうやら送信機の方でエラーが起こっているようで
[E][BLEAdvertising.cpp:169] setScanResponseData(): esp_ble_gap_config_scan_rsp_data_raw: 258 Unknown ESP_ERR error
というエラーのせいでなにかおかしくなっているっぽいです。
なので、仕方がなくディープスリープの機能を消します。対象としては、setup関数内の
pAdvertising->start();
Serial.println("Advertizing started...");
delay(10000);
pAdvertising->stop();
Serial.printf("enter deep sleep\n");
esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
Serial.printf("in deep sleep\n");
の部分です。
まぁ、loop関数内に"esp_deep_sleep"周りのコード以外を打ち込めばいい感じに動きます。
pAdvertising->start();
Serial.println("Advertizing started...");
delay(100);
pAdvertising->stop();
Serial.printf("enter deep sleep\n");
delay(900);
こんな感じ。これで定期的に送信ができるようになりました。
ちなみにですが、DeepSleepは、ESP32における目玉機能の一つ(当社比)です。
機能を発動すると超省電力で一定時間稼働させることができるので、単3電池3本で3ヶ月ぐらい稼働できる実績もあるものです。
ですが、今回はコンセントないしUSBポートで電源供給する可能性が高いので、今回はさっさと諦めました。
(コントリビューターの人、どうにかしてくれ…)
ということで、無事これで自作受信機で自作送信機を検知できるようになりました!!
最後に
ビーコン周りの技術は(個人的には)まだあまり自分の周りで使われることがないので、これから色々使われていってくれればなぁと思っています。
そして、ESP32のできる範囲には、改めて驚かされてます。
自分自身、本来はハードウェアの専攻じゃなく、VR関連の専攻なので、専門知識はまったくないのですが、ココまで調べながらの開発は面白かったなぁって思っています。
でも、できればハードウェアの人間にはなりたくないなぁ…(あくまでも趣味でやりたい)
就職もできてないんで、この記事読んで、ご興味が湧いた方! github、HP、Twitterとかみていただけたらと思います!!
12/12は僕の後輩兼同期(?)のyamakentocくんの記事です!おたのしみに!!
Author And Source
この問題について(【FUN Advent Calender 2020】こんな世の中なんで、ハードウェアでもいじって""""自己防衛"""していこう), 我々は、より多くの情報をここで見つけました https://qiita.com/yuzuka4573/items/2bac1277c8cc4151dee2著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .