Wifiビーコンとは
DJIのドローンはリモートIDの電波をWifiビーコンで飛ばしていると言うことで、
色々調べて見ました。
そもそもWifiビーコンとは何か調べて見ました。
Wi-Fiビーコン(Beacon)とは、無線LANのアクセスポイント(AP)が定期的に発信する管理フレームの一種で、AP自身の情報が含まれています。
と言う事のようです。
スマホなどで、接続先Wifiを設定する時に、接続可能なネットワーク名が色々出て来ますよね。 それが、Wifiビーコンのようです。
なので、Wifiが使える機器なら必ず持っている機能のようです。 BLE 5 Longrange 対応の機器は少ないので、こっちの方が、手軽なのでは無いでしょうか?
(なぜ、ほとんどのメーカーはBluetooth版だけなんだろうか。飛距離?価格?消費電力?)
フレーム構造
最初のFrame Controlの所は、
の2バイトです。
Typeの所にはフレームのタイプが入ります。
0:管理フレーム (Management Frame)
1:制御フレーム (Control Frame)
2:データフレーム (Data Frame)
Wifiビーコンの場合は、Typeが管理フレーム(0)で、Sub Typeが(8)に成るようです。
Elementsのところには、複数のエレメントが入ります。
Element IDは以下の意味を持ちます。
0 : SSID (名前)
1 : Supported Rates
2 : FH-parameter set
3 : DS parameter set
4 : CF parameter set
5 : TIM
6 : IBSS parameter set
7 : Country
8 : FH parameters
9 : FH Pattern Table
11 : BSS Load
12 : EDCA parameter set
32 : Power Constraint
35 : TPC Report
37 : Channel Switch Announcement
40 : Quiet
41 : IBSS DFS
42 : ERP Information
46 : QoS Capability
48 : RSN
50 : Extended supported Rates
221: Vendor Specific ベンダー(販売業者)が独自の情報を伝達するために用いるアトリビュート(属性)
必ず入れなければならないのは、ID=0 の SSIDエレメントです。あとはオプションです。
SSIDとは、Wifi AP の名前です。
DJI の Wifiビーコンを受信して見る
まずは、本当にRID電波を出しているか調べて見ます。
(もちろん機体登録してリモートIDを取得して、書き込み済みです。)
Android スマホに [OpenDroneID OSM] と言うアプリをインストールします。
このアプリ、Bleutoothだけかと思ったら、Wifiにも対応していたんですね。知らなかった。
アプリを起動して、DJIドローンをアームしてプロペラを回してください。アーム状態でないとRIDを発信しません。
アプリにDJIのRID情報が出るのを確認しました。
ビーコンのデータ内容を調べる
Seeed Studio XIAO ESP32C3 と言う以前も使ったマイコンでWifiビーコンを受信して見ます。
プログラムは [VS Code + PlatformIO] で開発します。
Wifiビーコンを受信するには、プロミスキャスモード(無差別モード)と言う、アンテナに届くフレームをすべて傍受することが可能なモードにする必要があるようです。
esp_wifi_set_promiscuous(true);
それでは、さっそく受信プログラムを書いてみましょう。
新規プロジェクトで、
Name: Wifi_Scan
Board: Seeed Studio XIAO ESP32C3
Framework: Arduino
とし、Platformio.iniファイルを以下のようにします。
[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200
main.cppに以下をコピペして下さい。
#include <Arduino.h>
#include <WiFi.h>
#include "esp_wifi.h"
#include "esp_mac.h"
#pragma pack(push,1) // データを詰めて配置
typedef struct{
uint16_t fragment_num:4; // bit[3-0] Fragment Number
uint16_t sequence_num:12; // bit[15-4] Sequence Number
} Seq_ctl;
typedef struct{
uint16_t fctl; // Frame Control
uint16_t duration = 0; // Duration
uint8_t da[6]; // Destination Address
uint8_t sa[6]; // Source Address
uint8_t bssid[6]; // BSSID
Seq_ctl seqctl; // Sequence number
uint64_t timestamp; // Timestamp
uint16_t interval; // Beacon interval
uint16_t cap; // Capability info
uint8_t payload[0];
} wifi_mac_hdr;
typedef struct{
uint8_t id; // Element ID
uint8_t len; // Length
uint8_t payload[0];
} element_head;
#pragma pack(pop)
#define maxCh 14 // max Channel -> US = 11, EU = 13, Japan = 14
int curChannel = 1;
const char *get_packet_name(wifi_promiscuous_pkt_type_t type)
{
switch(type) {
case WIFI_PKT_MGMT: return "MGMT";
case WIFI_PKT_DATA: return "DATA";
case WIFI_PKT_CTRL: return "CTRL";
case WIFI_PKT_MISC: return "MISC";
default:
return "";
break;
}
}
void rx_callback(void* buf, wifi_promiscuous_pkt_type_t type) {
int len,i;
char ssid[MAX_SSID_LEN+1];
wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t*)buf;
wifi_mac_hdr *mac =(wifi_mac_hdr *)ppkt->payload;
element_head *e =(element_head *)mac->payload;
vendor_ie_data_t *vi;
printf("PACKET TYPE = [ %s ], CHAN = %d, RSSI = %d\n", get_packet_name(type), ppkt->rx_ctrl.channel, ppkt->rx_ctrl.rssi);
printf("Frame Ctl = %04X\n",mac->fctl);
printf("Duration = %04X\n",mac->duration);
printf("DA = %02X:%02X:%02X:%02X:%02X:%02X\n", mac->da[0],mac->da[1],mac->da[2],mac->da[3],mac->da[4],mac->da[5]);
printf("SA = %02X:%02X:%02X:%02X:%02X:%02X\n", mac->sa[0],mac->sa[1],mac->sa[2],mac->sa[3],mac->sa[4],mac->sa[5]);
printf("BSSID = %02X:%02X:%02X:%02X:%02X:%02X\n", mac->bssid[0],mac->bssid[1],mac->bssid[2],mac->bssid[3],mac->bssid[4],mac->bssid[5]);
printf("Seq No. = %d, Frg No. = %d\n", mac->seqctl.sequence_num, mac->seqctl.fragment_num);
printf("Timestamp = %d\n",mac->timestamp);
printf("Interval = %dTU\n", mac->interval);
printf("Cpability = %04X\n", mac->cap);
len = ppkt->rx_ctrl.sig_len - sizeof(wifi_mac_hdr);
printf("Payload length = %d\n", len);
if(len <= 0) return;
while(len > 4){
printf("eid = %3d len = %3d ", e->id, e->len);
switch (e->id){
case 0: // SSID
if(e->len > MAX_SSID_LEN) break;
strncpy(ssid, (char*)e->payload, e->len);
ssid[e->len] = 0;
printf("SSID = %s\n", ssid);
break;
case 221: // Vender Specific
vi = (vendor_ie_data_t *)e;
printf("Vendor OUI = %02X:%02X:%02X OUI Type = %2d data = ",vi->vendor_oui[0],vi->vendor_oui[1],vi->vendor_oui[2],vi->vendor_oui_type);
for(i=0; i < vi->length; i++){
printf("%02X ",vi->payload[i]);
}
printf("\n");
break;
default:
printf("data = ");
for(i=0;i < e->len; i++){
printf("%02X ", e->payload[i]);
}
printf("\n");
break;
}
int dlen = 2 + e->len;
len -= dlen;
e = (element_head *)((uint8_t *)e + dlen);
}
uint8_t *fcs = (uint8_t *)e;
printf("FCS = %02X %02X %02X %02X\n\n",fcs[0],fcs[1],fcs[2],fcs[3]);
WiFi.scanDelete(); // 最後のスキャン結果をメモリから削除します。
}
void setup(){
Serial.begin(115200);
WiFi.mode(WIFI_STA);
esp_wifi_set_promiscuous(true); // プロミスキャスモード(無差別モード)に設定する。
esp_wifi_set_promiscuous_rx_cb(&rx_callback); // 受信したときの割り込み先設定
esp_wifi_set_channel(curChannel, WIFI_SECOND_CHAN_NONE);
}
void loop(){
curChannel++;
if(curChannel > maxCh) curChannel = 1;
ESP_ERROR_CHECK(esp_wifi_set_channel(curChannel, WIFI_SECOND_CHAN_NONE));
Serial.println("Changed channel:" + String(curChannel));
delay(1000);
}
これを XIAO ESP32C3 に焼いて、シリアルモニターで受信データを確認できます。
DJIドローンの信号を確認して見たら、送られてきたのは[SSID]と[Vendor Specific]の2つだけであることが解りました。
Vendor Specificの方にRID情報が入っているようです。
Vendor Specificエレメントの構造は以下のようになっています。
ここのデータ部分にRID情報が入っていました。
前回BLE版で作ったRIDデータのカウンタ部分から入っているようです。
また、OUIとOUI Typeの値は、RIDの場合、固定の数値のようです。
OUI[0] = 0xFA;
OUI[1] = 0x0B;
OUI[2] = 0xBC;
OUI_Type = 0x0D;
この数値どこで定義してるのか、ググっても見つけられませんでした。
OpenDroneIDアプリのソースコードでは、この値の時だけ受信処理をしているようです。
リモートID送信機(Wifi Beacon版)を作ってみよう
だいたい構造が解ったので、さっそく作って見ましょう。
用意するもの
① XIAO ESP32C3
② GNSS(GPS)モジュール
Wifi beacon で送信するのは SSID と Vendor エレメントだけです。
フレームデータを作ったら、送信する命令は、
esp_wifi_80211_tx(wifi_interface_t ifx, const void *buffer, int len, bool en_sys_seq);
を使うようです。
bufferにビーコンフレームの先頭アドレスを指定します。例えば以下のようになります。
esp_wifi_80211_tx( WIFI_IF_AP, &frame, sizeof(frame), true));
プログラミング
新規プロジェクトを作ります。
Name: RID_Wifi_Beacon
Board: Seeed Studio XIAO ESP32C3
Framework: Arduino
platformio.ini の中身を以下のようにします。
[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
board_build.mcu = esp32c3
framework = arduino
monitor_speed = 115200
board_build.partitions = huge_app.csv
lib_deps =
ArduinoJson
mikalhart/TinyGPSPlus@^1.0.3
me-no-dev/ESP Async WebServer@^1.2.4
https://github.com/me-no-dev/AsyncTCP
まず[espressif32]のバージョンを最新にしておきます。
更新の仕方はGUIまたは、CLIでやる方法があります。
GUIの方法は、下の[Home]ボタンを押して、
[Platforms]ボタンを押し、[Updates]タブをクリックします。
そこに [espressif32] が出てきたら、それをUpdateします。
CLIでのやり方は、下の[Terminal]ボタンを押し、ターミナルが出てきたらそこに以下の文字を入力しエンターします。
pio pkg update -g -p espressif32
これで、アップデート完了です。
それでは、プログラムを書いていきましょう。
main.cppを以下のようにします。
#include <Arduino.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include "esp_wifi.h"
#include <ESPAsyncWebServer.h>
#include <TinyGPSPlus.h>
#include "esp_mac.h"
static const char *TAG = "RemoteID";
#define SERIAL_NO "XYZ09876543210" // RIDの製造番号
#define REG_SYMBOL "JA.JU0987654321" // 無人航空機の登録記号
uint8_t ua_type = 2; // 機体種別 2 = ヘリコプター
const char *ssid_ap = "RemoteID-AP"; // WiFi AP の名前
const char *ssid_beacon = "RemoteID-Beacon";// WiFi Beacon の名前
const char *pass = "12345678"; // パスワード(8文字以上)
const IPAddress ip(192,168,6,1); // IPアドレス
const IPAddress subnet(255,255,255,0); // サブネットマスク
uint8_t mac[6];
// GNSS UART
#define RX_PIN 20 // GNSS の Txを接続するピン
#define TX_PIN 21 // GNSS の Rxを接続するピン
#define BOOT_BUTTON 9
#define GNSS_BAUDRATE 115200 // GNSS ボーレート ( GNSSモジュールの [デフォルト値] を設定して下さい )
// メッセージ種別
#define MSG_TYPE_BASIC_ID 0x0 // 製造番号、登録記号
#define MSG_TYPE_LOCATION 0x1 // 位置情報
#define MSG_TYPE_AUTH 0x2 // 認証情報
#define MSG_TYPE_SELF_ID 0x3 // Self-ID Message
#define MSG_TYPE_SYSTEM 0x4 // System Message
#define MSG_TYPE_OPERATOR_ID 0x5 // 操縦者情報
#define MSG_TYPE_PACK 0xF // パッケージ
// UAS ID Type
#define ID_TYPE_SerialNo 1 // 製造番号種別
#define ID_TYPE_ASSIGED_REG 2 // 登録番号種別
#define ID_TYPE_UUID 3 // UTM Assigned UUID
// 機体種別
#define UA_TYPE_NON 0 // なし
#define UA_TYPE_AEROPLANE 1 // 飛行機
#define UA_TYPE_HELICOPTER 2 // ヘリコプターorマルチローター
#define UA_TYPE_GYROPLANE 3 // ジャイロプレーン
#define UA_TYPE_HYBRID_LIFT 4 // ハイブリッドリフト(垂直離陸出来る固定翼機)
#define UA_TYPE_ORINITHOPTER 5 // 羽ばたき機(オルニソプター)
#define UA_TYPE_GLIDER 6 // グライダー(滑空機)
#define UA_TYPE_KITE 7 // カイト(凧)
#define UA_TYPE_FREE_BALLOON 8 // 自由気球
#define UA_TYPE_CAPITIVE_BALLOON 9 // 係留気球
#define UA_TYPE_AIRSHIP 10 // 飛行船
#define UA_TYPE_FREE_FALL_PARACHUTE 11 // パラシュート
#define UA_TYPE_ROCKET 12 // ロケット
#define UA_TYPE_TETHERED_POWERED_AIRCRAFT 13 // テザー式動力航空機
#define UA_TYPE_GROUND_OBSTACLE 14 // 地上障害物
#define UA_TYPE_OTHER 15 // その他
// 位置情報
#define LOC_STA_NON 0 // 不明
#define LOC_STA_GROUND 1 // 地面
#define LOC_STA_AIRBRONE 2 // 飛行中
#pragma pack(push,1) // データを詰めて配置
//------ PACK ここから ------------------------------------
typedef struct{
uint8_t ver:4; // bit[3-0] Protocol version
uint8_t type:4; // bit[7-4] message type
} Message_head;
typedef struct{
uint8_t speed_mul:1; // bit[0] 0:x0.25, 1:x0.75
uint8_t dir_seg:1; // bit[1] 0:<180, 1:>=180
uint8_t heght_type:1; // bit[2] 0:Abave Takeoff, 1:AGL
uint8_t resv:1; // bit[3] reserved
uint8_t status:4; // bit[7-4] status
} Status_flag;
typedef struct{
uint8_t horizontal:4; // bit[3-0]
uint8_t vetical:4; // bit[7-4]
} H_V_accuracy; // 水平垂直の正確さ
typedef struct{
uint8_t speed:4; // bit[3-0]
uint8_t baro:4; // bit[7-4]
}B_S_accuracy; // 速度方位の正確さ
typedef struct
{
uint8_t counter = 0; // Counter
Message_head msg; // message type Pack (0xf0)
uint8_t block_size = 25; // block size
uint8_t block_n = 4; // block count
//--- Basic ID (25byte)--------------------------
Message_head msg1; // BASIC_ID
uint8_t UA_type1:4; // bit[3-0] 機体種別
uint8_t ID_type1:4; // bit[7-4] ID_TYPE_SerialNo
char serial_no[20] = SERIAL_NO; // 製造番号
uint8_t resv1[3];
//--- Basic ID (25byte)--------------------------
Message_head msg2; // BASIC_ID
uint8_t UA_type2:4; // bit[3-0] 機体種別
uint8_t ID_type2:4; // bit[7-4] ID_TYPE_ASSIGED_REG
char reg_no[20] = REG_SYMBOL; // 登録記号 (例: JA.JU012345ABCDE)
uint8_t resv2[3];
//--- Location (25byte)--------------------------
Message_head msg3; // Location
Status_flag status; // 飛行中、方角E/W、速度倍率などの状態
uint8_t dir; // 方角
uint8_t speed; // 速度
uint8_t Ver_speed; // 垂直速度
uint32_t lat; // 緯度
uint32_t lng; // 経度
uint16_t Pressur_Altitude; // 気圧高度
uint16_t Geodetic_Altitude; // GPS高度
uint16_t Height; // 地面からの高さ
H_V_accuracy hv_Accuracy; // 水平垂直精度
B_S_accuracy bs_Accuracy; // 方位・速度精度
uint16_t timestamp; // 現在時刻の分以下の小数点1までの秒数x10
uint8_t T_Accuracy; // 時間精度(*0.1s)
uint8_t resv3;
//--- Page0 (25byte)-----------------------------
Message_head msg4; // 認証情報
uint8_t auth_type = 0x30; // Authentication Message [認証情報]
uint8_t page_count = 0; // Page0
uint8_t Length = 17; // headからのサイズ
uint32_t timestamp_auth; // 認証時刻?(2019.1.1からの秒数)
uint8_t auth_head = 0; // ヘッダ 0:AES-128bit-CCM
uint8_t auth_data[16] = {0}; // 認証データ(AES-128bit-CCM ってなんだ? 解からないので無視)
} RID_Data;
typedef struct{
uint16_t frag_num:4; // Fragment Number
uint16_t seq_num:12; // Sequence Number
} Seq_ctl;
typedef struct{
uint8_t eid = 0; // 0 = SSID, 参照[ https://research.ijcaonline.org/ctngc/number3/ctngc1027.pdf ]
uint8_t len = 32;
char ssid[32];
} SSID_element;
typedef struct{
uint8_t eid = 221; // Should be set to WIFI_VENDOR_IE_ELEMENT_ID (0xDD)
uint8_t len = 108; // 8 + 25 * 4; // Length of all bytes in the element data following this field. Minimum 4.
uint8_t oui[3]; // Vendor identifier (OUI).
uint8_t oui_type = 0x0d;// Open Drone ID
RID_Data rid; // RID data
} RID_vendor_element;
typedef struct{
uint16_t ver:2; // bit[1-0] protocol version
uint16_t type:2; // bit[3-2] Frame Type
uint16_t sub_type:4; // bit[7-4] Frame Sub type
uint16_t to_DS:1; // bit[8] to DS
uint16_t from_DS:1; // bit[9] from DS
uint16_t More_frag:1; // bit[10] More Fragment
uint16_t Retry:1; // bit[11] Retry
uint16_t Pwr_mgmt:1; // bit[12] Power Manegment
uint16_t more_data:1; // bit[13] More Data
uint16_t protexted:1; // bit[14] Protected
uint16_t order:1; // bit[15] Order
}Frame_control;
typedef struct{
uint16_t ess:1; // ESS Type Network
uint16_t ibss:1; // Not an IBSS Type Network
uint16_t cf:1; // CF Not Pollable
uint16_t cf_poll:1; // CF Poll Not Requested
uint16_t privacy:1; // Privacy Enabled(暗号化機能に関するフラグ)
uint16_t short_pr:1; // Short Preamble
uint16_t pbcc:1; // PBCC Not Allowed
uint16_t ch:1; // Channel Agility Not Used
uint16_t :2; // reserved
uint16_t gm_short:1; // G Mode Short Slot Time [9 microseconds]
uint16_t robust:1; // Robust Security Network Disabled
uint16_t :1; // reserved
uint16_t dssss:1; // DSSS-OFDM is Not Allowed
uint16_t :2; // reserved
} Capavility;
typedef struct{
Frame_control frame_ctr = { // Frame Control https://en.wikipedia.org/wiki/802.11_frame_types#Types_and_subtypes
.type = WIFI_PKT_MGMT, // MGMT
.sub_type = 8 // Beacon freme
};
uint16_t duration = 0; // Duration
uint8_t DA[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; // Destination Address
uint8_t SA[6]; // Source Address
uint8_t BSSID[6]; // BSSID
Seq_ctl seqctl; // Sequence number
uint64_t timestamp; // Timestamp
uint16_t interval = 100; // Beacon interval
Capavility cap={ // Capability info
.short_pr = 1,
.gm_short = 1
};
SSID_element se; // SSID element
RID_vendor_element ve; // Vendor Specific element
} Beacon_frame;
#pragma pack(pop)
//--- PACK ここまで------------------------------------------------------
TinyGPSPlus gps;
Beacon_frame beacon;
RID_Data *rid = &beacon.ve.rid;
time_t tm2019; // 1970.1.1から2019.1.1 までの秒数
String rid_file ="/rid.txt"; // SPIFFSのファイルデータ
String jsonTxt; // 保存するJson文字列
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
void setJson(){
uint8_t type = rid->UA_type1;
jsonTxt = "{\"sn\":\"" + String(rid->serial_no) + "\",";
jsonTxt += "\"rn\":\"" + String(rid->reg_no) + "\",";
jsonTxt += "\"type\":\"" + String(type) + "\"}";
}
void decodeJson(char *data){ // Jsonデータを解析
JsonDocument doc; // Jsonオブジェクト
uint8_t type;
DeserializationError error = deserializeJson(doc, data);
if(error){
Serial.println("JSONエラー");
}else{
const char* sn = doc["sn"]; // 製造番号
const char* rn = doc["rn"]; // 登録記号
strncpy(rid->serial_no, sn, 20); // 製造番号
strncpy(rid->reg_no, rn, 20); // 登録記号
type = doc["type"];
type %= 15;
rid->UA_type1 = type; // 機体種別
rid->UA_type2 = type; // 機体種別
setJson();
}
}
bool file_read(){ // SPIFFSからファイル読み込み
char buf[400];
bool res = false;
File fp = SPIFFS.open(rid_file, "r");
if (!fp){
printf("SPIFFS couldn't open\n");
}
else{
String str = fp.readStringUntil('\n');
fp.close();
int len = str.length();
if(len>0){
str.toCharArray(buf, len); // String を char 配列に変換
decodeJson(buf);
res = true;
}
}
return res;
}
bool file_write(){ // SPIFFへファイル書き込み
char buf[400];
File fp = SPIFFS.open( rid_file, "w");
if (!fp)
{
ESP_LOGE(TAG, "Failed to open file for writing");
return false;
}
fp.println(jsonTxt);
fp.close();
return true;
}
void set_speed(float s){ // 速度をセット
uint8_t v=0;
if(s <= 255*0.25){
v = (uint8_t)(s*4);
rid->status.speed_mul = 0;
}
else if(s >225*0.25 && s<254.25){
v = (uint8_t)((s-225*0.25)/0.75);
rid->status.speed_mul = 1;
}
else{
v = 254;
rid->status.speed_mul = 1;
}
rid->speed = v;
}
void set_direction(uint16_t way){ // 方向をセット
uint8_t d;
if(way<180){
d =(uint8_t)way;
rid->status.dir_seg = 0;
}
else{
d = (uint8_t)(way-180);
rid->status.dir_seg = 1;
}
rid->dir = d;
}
bool gps_read(void){ // GNSS読み込み
float lat,lng;
struct tm stm;
bool ok = false;
uint32_t time;
if (gps.location.isValid()){
lat = gps.location.lat(); // 緯度
lng = gps.location.lng(); // 経度
rid->lat = (uint32_t)(lat * 10000000);
rid->lng = (uint32_t)(lng * 10000000);
ok = true;
}
if (gps.date.isValid()){
stm.tm_year = gps.date.year()-1970; // 年
stm.tm_mon = gps.date.month()-1; // 月
stm.tm_mday = gps.date.day(); // 日
}
if (gps.time.isValid()){
stm.tm_hour = gps.time.hour(); // 時
stm.tm_min = gps.time.minute(); // 分
stm.tm_sec = gps.time.second(); // 秒
time = mktime( &stm ) - tm2019; // 現在時刻(2019.1.1からの秒数)
rid->timestamp = ( gps.time.minute()*60 + gps.time.second() ) * 10 + gps.time.centisecond()/10;
// ve->rid.timestamp_auth = time; // 認証時刻
beacon.timestamp = time;
ok = true;
}
if(ok){
set_speed(gps.speed.mps()); // 速度
set_direction(gps.course.deg()); // 方向
rid->Geodetic_Altitude = (gps.altitude.meters()+1000)*2; // GPS高度
}
return ok;
}
void frame_init() // Beacon freme の初期化
{
struct tm stm;
stm.tm_year = 2019 - 1970;
stm.tm_mon = 0;
stm.tm_mday = 1;
stm.tm_hour = 0;
stm.tm_min = 0;
stm.tm_sec = 0;
tm2019 = mktime( &stm );
rid->status.heght_type = LOC_STA_GROUND;
rid->lat =(uint32_t)(35.6807068 * 10000000); // 初期値(皇居広場)
rid->lng =(uint32_t)(139.7572909 * 10000000);
esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP); // MACアドレスを得る
memcpy(beacon.SA, mac, 6); // SA Address 設定
memcpy(beacon.BSSID, mac, 6); // BSSID 設定
printf("Wifi AP MAC Address = %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
strcpy(beacon.se.ssid, ssid_beacon); // beacon ssid 設定
RID_vendor_element *ve = &beacon.ve;
ve->oui[0] = 0xFA; // RID Vendor OUI
ve->oui[1] = 0x0B; // RID Vendor OUI
ve->oui[2] = 0xBC; // RID Vendor OUI
ve->oui_type = 0x0D;
ve->rid.msg.type = MSG_TYPE_PACK; // Message Type = パッケージ
ve->rid.msg1.type = MSG_TYPE_BASIC_ID;
ve->rid.ID_type1 = ID_TYPE_SerialNo; // シリアルNO.
ve->rid.UA_type1 = ua_type; // 機体種別
ve->rid.msg2.type = MSG_TYPE_BASIC_ID;
ve->rid.ID_type2 = ID_TYPE_ASSIGED_REG; // 登録記号
ve->rid.UA_type2 = ua_type; // 機体種別
ve->rid.msg3.type = MSG_TYPE_LOCATION; // Location
ve->rid.hv_Accuracy.horizontal = 11; // 水平精度 (<3m)
ve->rid.hv_Accuracy.vetical = 0; // 垂直精度 (Unknown)
ve->rid.bs_Accuracy.baro = 0; // 方位精度 (Unknown)
ve->rid.bs_Accuracy.speed = 4; // 速度精度 (<0.3m/s)
ve->rid.msg4.type = MSG_TYPE_AUTH;
}
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT: // 接続されたとき
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
setJson();
ws.textAll(jsonTxt); // 登録データ送信
break;
case WS_EVT_DISCONNECT: // 切断されたとき
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA: // テキストデータが受信されたとき
Serial.printf("Data received\n");
decodeJson((char *)data); // データを解析する
file_write(); // SPIFFSデータ書き換え
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
void setup()
{
Serial.begin(115200);
if(!SPIFFS.begin(true)){ // SPIFFSのセットアップ
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
Serial1.begin(GNSS_BAUDRATE, SERIAL_8N1, RX_PIN, TX_PIN); // GNSSモジュールの接続設定
while(!Serial1){}
frame_init(); // Beacon frameを初期化
file_read(); // SPIFFS から保存データを Becon frame に読み込む
//int a= esp_wifi_set_protocol( WIFI_IF_AP, WIFI_PROTOCOL_LR );
//Serial.println(a);
WiFi.softAP(ssid_ap, pass); // Wifiをアクセスポイントにする
delay(100);
WiFi.softAPConfig(ip, ip, subnet);
WiFi.setTxPower( WIFI_POWER_19_5dBm ); // 出力パワー設定
ws.onEvent( onWsEvent ); // Websocketのイベント割り込み先設定
server.addHandler(&ws);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html");
});
server.begin();
}
void loop()
{
ws.cleanupClients(); // Websocket 更新
while(Serial1.available()){ // GNSSからデータが来たら
if(gps.encode(Serial1.read())){
gps_read(); // gpsデータを読み込む
}
}
beacon.seqctl.seq_num++; // Update sequence number
rid->counter++; // Update RID counter
ESP_ERROR_CHECK( esp_wifi_80211_tx( WIFI_IF_AP, &beacon, sizeof(Beacon_frame), true)); // RID情報を含んだ Beacon frameを送信
delay(50);
}
RID情報は 製造番号、登録記号、位置情報、認証情報の4つは最低送らなければならないのですが、認証情報のところは分からなかったので、0データを送っています。
スマホからRID情報を書き換えられるようにするために、接続されたときに表示するHTMLファイルを作ります。
[data]フォルダを新たに作り、そこにindex.htmlというファイルを新たに作ります。
その中身を以下のようにします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>RID登録</title>
</head>
<style>
body {font-size: 1rem; background-color:rgba(128,128,128,0.322);}
h2 {font-size: 1.2rem;color:rgb(34, 0, 255); text-align: center; background-color: #aaffff;}
p {margin-bottom: 0px;}
input {font-size: 1rem;}
select {font-size: 1rem;}
div {margin-top: 20px; text-align: center}
</style>
<!-----------------------HTML----------------------pattern="[A-Z0-9\.]*" -------------->
<body>
<h2>RID情報 書換えフォーム</h2>
<p>製造番号:</p>
<input type="email" id="sn" maxlength="20" aria-label="SN" inputmode="verbatim" autocomplete="off" pattern="[A-Z0-9\.]*" oninput="value = value.toUpperCase();">
<p>登録記号:</p>
<input type="email" id="rn" maxlength="20" aria-label="RN" inputmode="verbatim" autocomplete="off" pattern="[A-Z0-9\.]*" oninput="value = value.toUpperCase();">
<p>機体種別:</p>
<select id="type" aria-label="type">
<option value="0">なし</option>
<option value="1">飛行機</option>
<option value="2">ヘリorマルチローター</option>
<option value="3">ジャイロプレーン</option>
<option value="4">ハイブリッドリフト</option>
<option value="5">羽ばたき機</option>
<option value="6">グライダー</option>
<option value="7">カイト(凧)</option>
<option value="8">自由気球</option>
<option value="9">係留気球</option>
<option value="10">飛行船</option>
<option value="11">パラシュート</option>
<option value="12">ロケット</option>
<option value="13">テザー式動力航空機</option>
<option value="14">地上障害物</option>
<option value="15">その他</option>
</select>
<div>
<input type="button" value=" 送 信 " onclick="buttonClick()" style="width: 200px; background-color: aquamarine;"/>
</div>
<div id="msg" style="color: #00aa00;"></div>
</body>
<!--------------------JavaScript---------------------------------->
<script>
var ws = null;
InitWebSocket();
document.getElementById('rn').value = 'JA.';
// jsonデータのテンプレート
var json_data = {
sn: '',
rn: '',
type: 0
};
function buttonClick(){ // 送信ボタンが押されたら
// JSONデータの作成
var send_json = json_data;
send_json.sn = document.getElementById("sn").value;
send_json.rn = document.getElementById("rn").value;
send_json.type = document.getElementById("type").value;
send_json = JSON.stringify(send_json);
if(ws != null){
ws.send(send_json); // websocketでデータ送信
document.getElementById("msg").innerText = "送信しました。";
}else{
document.getElementById("msg").innerText = "送信できませんでした。";
}
};
function InitWebSocket() // Websocket設定
{
ws = new WebSocket("ws://" + location.hostname + ":80/ws");
ws.onmessage = function(evt){ // データが送られてきた時の処理
JSONobj = JSON.parse(evt.data);
document.getElementById('sn').value = JSONobj.sn;
document.getElementById('rn').value = JSONobj.rn;
document.getElementById('type').value = JSONobj.type;
}
ws.onclose = function(evt) { // Websocketが閉じたときの処理
ws.close();
ws = null
}
};
</script>
</html>
このファイルをESP32C3にアップロードします。
PCにESP32C3を接続して、[BOOT]ボタンを押しながら[RESET]ボタンをチョンと押し、[BOOT]ボタンを離します。すると書き込みモードになるので、下図の順番でクリックするだけです。
②を押した後に、ターミナルに"=====[SUCSESS]====="と表示されれば転送完了です。
③を押してエクスプローラーの表示に戻します。
飛距離はどうなの?
実際作って見て、測定したところ、なんと1km離れたところで受信確認できました。
Wifiってそんなに飛ぶイメージ無かったので以外でした。このモジュールがすごいのかな?
消費電力は?
USBの電流が測れる器具を間にかませて、測定した所、
Wifi 版は 約0.1A で
BLE 版は 約0.13Aでした。
今回の場合、Wifi版の方が消費電力が少なかったです。
RID情報の書き換え方
スマホのWiFi接続先を[RemoteID-AP]にして、パスワードに[12345678]と入力します。
(スマホに名前が表示されるまで、結構時間がかかる場合がありますが、待ってれば出てくると思います。)
機種によってはもっと質の良いWiFiに繋ぎたい見たいなメッセージが出る場合があります。このときは[許可]ではなく[拒否]を選択して下さい。
スマホのWebブラウザを立ち上げ、URLに[192.168.6.1]と入力します。
すると下図のように表示されるはずです。
内容を書き換えて、[送信]ボタンを押すと書き込まれます。
最後にお願い
これを作成又は使用したことにより、何らかの問題が発生したとしても、私は一切責任を負いませんので、あくまで自己責任でお願い致します。