リモートIDを自作する（Wifi ビーコン版）

### Wifiビーコンとは
　DJIのドローンはリモートIDの電波をWifiビーコンで飛ばしていると言うことで、
色々調べて見ました。
　そもそもWifiビーコンとは何か調べて見ました。
 
> Wi-Fiビーコン（Beacon）とは、無線LANのアクセスポイント（AP）が定期的に発信する管理フレームの一種で、AP自身の情報が含まれています。
 
 と言う事のようです。
 　スマホなどで、接続先Wifiを設定する時に、接続可能なネットワーク名が色々出て来ますよね。 それが、Wifiビーコンのようです。
  
  なので、Wifiが使える機器なら必ず持っている機能のようです。 BLE 5 Longrange 対応の機器は少ないので、こっちの方が、手軽なのでは無いでしょうか？
 （なぜ、ほとんどのメーカーはBluetooth版だけなんだろうか。飛距離？価格？消費電力？）

 ### フレーム構造
 Wifiビーコンのフレーム構造を調べて見ました。
 ![Wifi_beacon_frame.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/a360a54a-ca5e-12f4-e1f4-68ce101c9a7b.jpeg)

最初のFrame Controlの所は、
![fctl.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/dbe89139-a2b8-7a6b-6c67-f676ac236211.jpeg)
の２バイトです。
Typeの所にはフレームのタイプが入ります。
　０：管理フレーム (Management Frame)
　１：制御フレーム (Control Frame)
　２：データフレーム (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]の２つだけであることが解りました。
Vendor Specificの方にRID情報が入っているようです。

Vendor Specificエレメントの構造は以下のようになっています。
![Vendor.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/2b640e14-155b-6201-bd91-47a67805119b.jpeg)
ここのデータ部分にRID情報が入っていました。
[前回BLE版](https://qiita.com/kobatan/items/082565f03f8c56276129)で作った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)モジュール

接続は以下のようにします。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/d12e0517-89f1-eb3f-a987-1d88e1860dff.jpeg">

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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/5a56ff35-5898-a606-5767-4243b1125669.jpeg)
![platformsUpdate.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/bfa2dae7-e868-a436-fe6a-7dcdd1506273.jpeg)
[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;				// 現在時刻の分以下の小数点１までの秒数ｘ10
	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情報は 製造番号、登録記号、位置情報、認証情報の４つは最低送らなければならないのですが、認証情報のところは分からなかったので、０データを送っています。


スマホからRID情報を書き換えられるようにするために、接続されたときに表示するHTMLファイルを作ります。
[**data**]フォルダを新たに作り、そこに**index.html**というファイルを新たに作ります。
![dataFolder.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/b3be512f-8f01-19f9-61b8-755e388d0f78.jpeg)

その中身を以下のようにします。
```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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/13295223-7bd4-a9be-dfda-fd03d51cd44f.jpeg)

メインプログラムも書き込みましょう。
右矢印をクリック。
![書き込み.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/6264ccc6-26e5-5440-104d-aa3f19a0aa3f.jpeg)
### 飛距離はどうなの？
実際作って見て、測定したところ、なんと１ｋｍ離れたところで受信確認できました。
Wifiってそんなに飛ぶイメージ無かったので以外でした。このモジュールがすごいのかな？
### 消費電力は？
USBの電流が測れる器具を間にかませて、測定した所、
　Wifi 版は　約0.1A で
　BLE 版は　約0.13Aでした。
今回の場合、Wifi版の方が消費電力が少なかったです。

### RID情報の書き換え方
　スマホのWiFi接続先を[**RemoteID-AP**]にして、パスワードに[**12345678**]と入力します。
（スマホに名前が表示されるまで、結構時間がかかる場合がありますが、待ってれば出てくると思います。）
　機種によってはもっと質の良いWiFiに繋ぎたい見たいなメッセージが出る場合があります。このときは[許可]ではなく[**拒否**]を選択して下さい。
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/40f2fa9d-365b-1cfe-43aa-dc21c563b68c.jpeg">
スマホのWebブラウザを立ち上げ、URLに[**192.168.6.1**]と入力します。
すると下図のように表示されるはずです。


<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520758/b6ea39f2-8a18-1439-282a-65d37314b503.jpeg">

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

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