0
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?

リモートIDを自作する(Wifi ビーコン版)

Last updated at Posted at 2024-09-16

Wifiビーコンとは

 DJIのドローンはリモートIDの電波をWifiビーコンで飛ばしていると言うことで、
色々調べて見ました。
 そもそもWifiビーコンとは何か調べて見ました。

Wi-Fiビーコン(Beacon)とは、無線LANのアクセスポイント(AP)が定期的に発信する管理フレームの一種で、AP自身の情報が含まれています。

と言う事のようです。
 スマホなどで、接続先Wifiを設定する時に、接続可能なネットワーク名が色々出て来ますよね。 それが、Wifiビーコンのようです。

なので、Wifiが使える機器なら必ず持っている機能のようです。 BLE 5 Longrange 対応の機器は少ないので、こっちの方が、手軽なのでは無いでしょうか?
(なぜ、ほとんどのメーカーはBluetooth版だけなんだろうか。飛距離?価格?消費電力?)

フレーム構造

Wifiビーコンのフレーム構造を調べて見ました。
Wifi_beacon_frame.jpg

最初のFrame Controlの所は、
fctl.jpg
の2バイトです。
Typeの所にはフレームのタイプが入ります。
 0:管理フレーム (Management Frame)
 1:制御フレーム (Control Frame)
 2:データフレーム (Data Frame)

Wifiビーコンの場合は、Typeが管理フレーム()で、Sub Typeが()に成るようです。

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ファイルを以下のようにします。

platformio.ini
[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200

main.cppに以下をコピペして下さい。

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エレメントの構造は以下のようになっています。
Vendor.jpg
ここのデータ部分に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 の中身を以下のようにします。

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]ボタンを押して、
home.jpg
platformsUpdate.jpg
[Platforms]ボタンを押し、[Updates]タブをクリックします。
そこに [espressif32] が出てきたら、それをUpdateします。

CLIでのやり方は、下の[Terminal]ボタンを押し、ターミナルが出てきたらそこに以下の文字を入力しエンターします。

pio pkg update -g -p espressif32

これで、アップデート完了です。

それでは、プログラムを書いていきましょう。
main.cppを以下のようにします。

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というファイルを新たに作ります。
dataFolder.jpg

その中身を以下のようにします。

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]====="と表示されれば転送完了です。
③を押してエクスプローラーの表示に戻します。
upload.jpg

メインプログラムも書き込みましょう。
右矢印をクリック。
書き込み.jpg

飛距離はどうなの?

実際作って見て、測定したところ、なんと1km離れたところで受信確認できました。
Wifiってそんなに飛ぶイメージ無かったので以外でした。このモジュールがすごいのかな?

消費電力は?

USBの電流が測れる器具を間にかませて、測定した所、
 Wifi 版は 約0.1A で
 BLE 版は 約0.13Aでした。
今回の場合、Wifi版の方が消費電力が少なかったです。

RID情報の書き換え方

 スマホのWiFi接続先を[RemoteID-AP]にして、パスワードに[12345678]と入力します。
(スマホに名前が表示されるまで、結構時間がかかる場合がありますが、待ってれば出てくると思います。)
 機種によってはもっと質の良いWiFiに繋ぎたい見たいなメッセージが出る場合があります。このときは[許可]ではなく[拒否]を選択して下さい。

スマホのWebブラウザを立ち上げ、URLに[192.168.6.1]と入力します。
すると下図のように表示されるはずです。

内容を書き換えて、[送信]ボタンを押すと書き込まれます。

最後にお願い

 これを作成又は使用したことにより、何らかの問題が発生したとしても、私は一切責任を負いませんので、あくまで自己責任でお願い致します。

0
1
3

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
0
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?