7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

リモートIDを自作する(パート2)

Last updated at Posted at 2023-03-05

100g以上の無人航空機に義務化されたリモートIDを自作できないかやってみた。(パート2)

前回は、RID情報をプログラム内に記録させていたため、登録記号などの情報を書き換える時、プログラムを書き換える必要がありました。
 今回は、RID情報をSPIFFS領域に記録するようにしたので、プログラムを書き換えることなく変更できます。スマホアプリも必要なく、スマホからWiFiで接続して書き換えます。(このやり方は、正規の方法とは全く異なります。)

あと、ちゃんと送信されているか確認用のRID受信機の作り方はこちらです。

用意するもの

① マイコン Seeed XIAO ESP32C3 (スイッチサイエンス 902円)
② GNSSモジュール Flywoo GOKU GM10 Nano V3 GPS (HELIMONStER 2890円)
③ USB-C電源 又は 1Sリポバッテリー
④ 熱収縮チューブ

 XIAO ESP32C3 は Wifi,Bluetooth5(BLE) 内蔵の超小型マイコンです(外付けアンテナ付き)技適もバッチリついています。
 背面には1Sリポバッテリーの接続端子がありそこからの起動もできます。なおかつ充電回路も内蔵してるのでUSBからバッテリーを充電できます。

 BLEの最大出力は15dBあり、私の測定では940m飛ぶことを確認しています。(2階の窓に置いて、そこからの見通し出来る距離が最高940mでした。それ以上は障害物があり測定できませんでした。もしかしたらもっと飛ぶかもしれません。1.5kmぐらい真っすぐな道路を見つけたので、割りばしの先に発信機を取り付けて道端に刺し測定したら300mぐらいしか飛びませんでした。地上から約20cmぐらいでは、道路の反射などが影響するのでしょうか?良く分かりませんが距離が出ませんでした。)

配線は以下のようにして下さい。

GNSSの電源は5Vでも3.3Vでもいいのですが、リポバッテリーでも起動できるように3.3Vの方に接続します。
外部電源を3.7Vから取るときは、写真のように1Sリボバッテリー用コネクタを別途取り付けてください。
5Vから電源を取るときは、5V端子にダイオードをかまして5V電源を接続するようにマニュアルには書かれていました。

プログラム

 前回は[Arduino IDE]でプログラムしましたが、今回は[Visual Studio Code]+[PlatformIO]の開発環境でプログラムします。SPIFFS領域を使用したかったからです。

[VS Code]+[PlatformIO]のインストールはこちらを参考にしました。
新規プロジェクトでは下図のように設定して下さい。

[platformio.ini]の中身は以下のようにして下さい。
追加ライブラリーやシリアルモニタの速度、パーテッションの指定をしています。
(プログラム領域の右上のアイコンをクリックすると全コピーできます。)

platformio.ini
[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
lib_deps = 
	mikalhart/TinyGPSPlus@^1.0.3
	ottowinter/ESPAsyncWebServer-esphome@^3.0.0
	links2004/WebSockets@^2.3.7
	bblanchon/ArduinoJson@^6.20.1
monitor_speed = 115200
board_build.partitions = huge_app.csv

dataフォルダを新たに作成します。
[RemoteID]のところを右クリックして、[新しいフォルダ]を選択し、フォルダ名を[data]にします。

 次に[data]フォルダ内に新たに[index.html]ファイルを作ります。
[data]フォルダ名の上でマウスの右クリックをして、「新しいファイル」を選択し、ファイル名を[index.html]にします。中身は以下です。

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="text" id="name" maxlength="28" aria-label="name">
		<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">ヘリコプター</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="butotnClick()" style="width: 200px; background-color: aquamarine;" />
		</div>
		<div id="msg" style="color: #00aa00;"></div>
		<div id="time"></div>
    
	</body>
	<!--------------------JavaScript---------------------------------->
	<script>
		var ws = null;
		InitWebSocket();
		
		document.getElementById('rn').value = 'JA.';
		let msg = document.getElementById('msg');

	 	// jsonデータのテンプレート
 		var json_data = {
            name: '',
			sn: '',
			rn: '',
			type: 0
        };

		function butotnClick(){											// 送信ボタンが押されたら
  		// JSONデータの作成
			var send_json = json_data;
            send_json.name = document.getElementById("name").value;
			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でデータ送信
			msg.innerText = '送信しました。';
		};

		function InitWebSocket()										// Websocket設定						
  	    {
			ws = new WebSocket('ws://'+window.location.hostname+':81/');
			
			ws.onmessage = function(evt){					            // データが送られてきた時の処理
				JSONobj = JSON.parse(evt.data);
				document.getElementById('name').value = JSONobj.name;
				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 
			}
		};
		
		var cnt = 180;
		PassageID = setInterval('countdown()',1000);   // タイマーをセット(1000ms間隔)
		function countdown(){
			cnt--;
			var msg = "残り" + cnt +"秒で終了します。";
			document.getElementById("time").innerHTML = msg;
		}
	</script>
</html>

この[data]フォルダーの中身をESP32C3のSPIFFS領域にアップロードします。

PCにESP32C3を接続して、[BOOT]ボタンを押しながら[RESET]ボタンをチョンと押し、[BOOT]ボタンを離します。すると書き込みモードになるので、下図の順番でクリックするだけです。
②を押した後に、ターミナルに"=====[SUCSESS]====="と表示されれば転送完了です。
③を押してエクスプローラーの表示に戻します。

そうしましたら後はプログラムを書くだけです。
[src]の[main.cpp]の中身に下記のコードをコピペして下さい。

main.cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEAdvertising.h>
#include <TinyGPSPlus.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BLEScan.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <WebSocketsServer.h>
#include <SPIFFS.h>
#include <FS.h>
#include <ArduinoJson.h>

#define SERIAL_NO	   "1234A0123456789"	// RIDの製造番号 
#define REG_SYMBOL	   "JA.JU012345ABCDE"	// 無人航空機の登録記号
#define AUTHENTICATION {0}					// 認証情報
#define LOCAL_NAME 	   "My DRONE"			// 名前
uint8_t addr_coded[6] = {0xC0, 0xDE, 0x52, 0x00, 0x00, 0x01};	// BLE Address (上位2bit[11] = Random Address:Static Device Address) 他の人と被らない数値を設定して下さい。

const char *ssid="my_RemoteID";				// WiFi AP の名前
const char *pass="12345678";		   		// パスワード(8文字以上)
const IPAddress ip(192,168,6,1);	   		// IPアドレス
const IPAddress subnet(255,255,255,0); 	    // サブネットマスク

#define BAUDRATE	115200		// GNSS ボーレート
#define RATE		100 		// GNSS 更新レート 100ms(10Hz)

#define RX_PIN 20
#define TX_PIN 21
#define BOOT_BUTTON 9

// AD Type
#define AD_TYPE_FLAG			0x01	// 発信モード指定
#define AD_TYPE_SHT_LOCAL_NAME	0x08	// 短縮名
#define AD_TYPE_CMP_LOCAL_NAME	0x09	// 名前
#define AD_TYPE_TX_POWER		0x0A	// 送信出力
#define AD_TYPE_SERVICE_DATA    0x16	// 16-bit UUID

// メッセージ種別
#define MSG_TYPE_BASIC_ID		0x00	// 製造番号、登録記号
#define MSG_TYPE_LOCATION		0x10	// 位置情報
#define MSG_TYPE_AUTH			0x20	// 認証情報
#define MSG_TYPE_SELF_ID		0x30	//
#define MSG_TYPE_SYSTEM			0x40	//
#define MSG_TYPE_OPERATOR_ID	0x50	// 操縦者情報
#define MSG_TYPE_PACK  			0xF0	// パッケージ

// UAS ID Type
#define ID_TYPE_SerialNo 		0x10	// 製造番号種別
#define ID_TYPE_ASSIGED_REG		0x20	// 登録番号種別
#define ID_TYPE_UUID			0x30	// UTM Assigned UUID

// 機体種別
#define UA_TYPE_NON						  0		// なし
#define UA_TYPE_AEROPLANE				  1		// 飛行機
#define UA_TYPE_HELICOPTER				  2		// ヘリコプター
#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			0x00	// 不明
#define LOC_STA_GROUND		0x10	// 地面
#define LOC_STA_AIRBRONE	0x20	// 飛行中

#define LOC_FLAG_HT			0x04	// Height Type 0:Above Takeoff 1:AGL(Above Ground Level)地面からの高度
#define LOC_FLAG_EW 		0x02	// Eeast/West Direction 0:<180 1>=180
#define LOC_FLAG_SM 		0x01	// Speed Multiplier 0:x0.25 1:x0.75

// ADV データ
#define N  4	// メッセージ個数

#pragma pack(push,1)	// データを1バイト単位に詰めて配置
typedef struct{
	uint8_t AD_leg 		= 5 + 3 + N*25;			// サービスデータのサイズ
	uint8_t AD_type 	= AD_TYPE_SERVICE_DATA;	// Service Data
	uint16_t AD_UUID 	= 0xFFFA;				// ASTM
	uint8_t App_code 	= 0x0D;					// Open Drone ID
	uint8_t counter;
	//--- package ---------------------------	
		uint8_t type_pack = MSG_TYPE_PACK;		// pack
		uint8_t msg_size	= 25;
		uint8_t n = N;
		//--- Basic ID (25byte)--------------------------
			uint8_t type_basic1 = MSG_TYPE_BASIC_ID;	// Basic ID
			uint8_t ID_UA_type1 = (ID_TYPE_SerialNo | UA_TYPE_HYBRID_LIFT);	// UA[b3-b0]に機体種別			
			char UAS_ID1[20] = SERIAL_NO;		        // 製造番号
			uint8_t resv1[3];
		//--- Basic ID (25byte)--------------------------	
			uint8_t type_basic2 = MSG_TYPE_BASIC_ID;	// Basic ID
			uint8_t ID_UA_type2 = ID_TYPE_ASSIGED_REG;
			char UAS_ID2[20] = REG_SYMBOL;	            // 登録記号 (例: JA.JU012345ABCDE)
			uint8_t resv2[3];
		//--- Location (25byte)--------------------------		
			uint8_t type_loc = MSG_TYPE_LOCATION;		// Location
			uint8_t 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;						    // 地面からの高さ
			uint8_t V_H_Accuracy = 0x0B;                // 精度(水平<3m)
			uint8_t B_S_Accuracy = 0x04;                // 精度(速度<0.3m/s)
			uint16_t timestamp = 0;			            // 現在時刻の分以下の小数点1までの秒数x10
			uint8_t T_Accuracy = 1;		                // 時間精度(0.1s)
			uint8_t resv3;
		//--- Page0 (25byte)-----------------------------				
			uint8_t type_auth = MSG_TYPE_AUTH;	        // 認証情報
			uint8_t Auth_Type = 0x30;		            // Authentication Message [認証情報]
			uint8_t page_count = 0;			            // Page0
			uint8_t Length = 17;                        // headからのサイズ
			uint32_t timestamp_auth = 0;	            // 現在時刻(2019.1.1からの秒数)
            uint8_t auth_head = 0;                      // ヘッダ 0:AES-128bit-CCM
			char auth_data[16] = AUTHENTICATION;	    // 認証データ(AES-128bit-CCM ってなんだ? 解からないので無視)
//---- local name -----------------------------
	uint8_t l = 29;						     // Byte数
	uint8_t t = AD_TYPE_CMP_LOCAL_NAME;	     // AD_type = Complete local name
	char local_name[28] = LOCAL_NAME;	     // 名前
//---- Shortened local name -----------------------------
	uint8_t l2 = 4;							// Byte数
	uint8_t t2 = AD_TYPE_SHT_LOCAL_NAME;	// AD_type = Shortened local name
	char sat[3];    						// 補足衛星数	
} Adv_Data;
#pragma pack(pop)

TinyGPSPlus gps;
Adv_Data adv;
time_t tm2019;	             // 1970.1.1から2019.1.1 までの秒数
String rid_file ="/rid.txt"; // SPIFFSのファイルデータ
String jsonTxt;				 // 保存するJson文字列

void setJson(){
	jsonTxt ="{\"name\":\""	 + String(adv.local_name) +	"\",";
	jsonTxt += "\"sn\":\""	 + String(adv.UAS_ID1) +	"\",";
	jsonTxt += "\"rn\":\""	 + String(adv.UAS_ID2) +	"\",";
	jsonTxt += "\"type\":\"" + String(adv.ID_UA_type1 & 0x0f) +	"\"}";
}

void decodeJson(char *data){		// Jsonデータを解析
	StaticJsonDocument<100> doc;	// Jsonオブジェクトの初期化
	uint8_t d;
	int v;

	DeserializationError error = deserializeJson(doc, data);
	if(error){
		Serial.println("JSONエラー");
	}else{
	    const char* name = doc["name"];			// 名前
		const char* sn = doc["sn"];				// 製造番号		
		const char* rn = doc["rn"];				// 登録記号
		memcpy(adv.local_name, name, 28);		
		memcpy(adv.UAS_ID1, sn, 20);				
		memcpy(adv.UAS_ID2, rn, 20);				
		d = adv.ID_UA_type1 & 0xF0;
		v = doc["type"];
		v %= 15;
		adv.ID_UA_type1 = d | (uint8_t)v;		// 機体種別
		setJson();
	}
}

bool file_read(){								// SPIFFSからファイル読み込み
	char buf[100];
	File f = SPIFFS.open(rid_file, "r");
	if(!f){
		return false;
	}
	String data = f.readStringUntil('\n');
	data.toCharArray( buf, data.length());
	decodeJson(buf);
	f.close();
	return true;
}

bool file_write(){								// SPIFFへファイル書き込み
	char buf[100];
	File f = SPIFFS.open(rid_file, "w");
	if(!f){
		return false;
	}
	f.println(jsonTxt);
	f.close();
	return true;
}

void adv_data_init(){							// ADVデータ初期化
  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 );

  adv.status = LOC_STA_GROUND;
  adv.lat =(uint32_t)(35.6807068 * 10000000);	// 初期値(皇居広場)
  adv.lng =(uint32_t)(139.7572909 * 10000000);

  file_read();							    	// SPIFFS から保存データを読み込む
}

void set_speed(float  s){					// 速度をセット
	uint8_t v=0;
	if(s <= 255*0.25){
		v = (uint8_t)(s*4);
		adv.status &= ~LOC_FLAG_SM;	
	}
	else if(s >225*0.25 && s<254.25){
		v = (uint8_t)((s-225*0.25)/0.75);
		adv.status |= LOC_FLAG_SM;		
	}
	else{
		v = 254;
		adv.status |= LOC_FLAG_SM;				
	}
	adv.speed = v;
}

void set_direction(uint16_t way){	        // 方向をセット
	uint8_t d;
	if(way<180){
		d =(uint8_t)way;
		adv.status &= ~LOC_FLAG_EW;
	}
	else{
		d = (uint8_t)(way-180);
		adv.status |= LOC_FLAG_EW;		
	}
	adv.dir = d;
}

bool gps_read(void){						// GNSS読み込み
  float lat,lng;
  struct tm stm;
  bool ok = false;
  uint32_t time;

  if( gps.satellites.isValid()){		    // 衛星補足数
	sprintf(adv.sat, "%d", gps.satellites.value());	
  }
  else sprintf(adv.sat, "%d", 0);	 	
  
  if (gps.location.isValid()){
  	lat = gps.location.lat();				// 緯度
  	lng = gps.location.lng();				// 経度
  	adv.lat = (uint32_t)(lat * 10000000);
  	adv.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からの秒数
    adv.timestamp =  ( gps.time.minute()*60 + gps.time.second() ) * 10 + gps.time.centisecond()/10;
	adv.timestamp_auth = time;			    // 現在時刻
  	ok = true;
  }

  if(ok){
   	set_speed(gps.speed.mps());								// 速度
	set_direction(gps.course.deg());						// 方向  
  	adv.Geodetic_Altitude = (gps.altitude.meters()+1000)*2;	// GPS高度
  }
  return ok;
}

/*
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED      (0 << 0) // Non-Connectable and Non-Scannable Undirected advertising
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE                          (1 << 0) // Connectable advertising
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE                            (1 << 1) // Scannable advertising
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_DIRECTED                             (1 << 2) // Directed advertising
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_HD_DIRECTED                          (1 << 3) // High Duty Cycle Directed Connectable advertising (<= 3.75 ms Advertis- ing Interval)
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY                               (1 << 4) // Use legacy advertising PDUs
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_ANON_ADV                             (1 << 5) // Omit advertiser's address from all PDUs ("anonymous advertising")
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_INCLUDE_TX_PWR                       (1 << 6) // Include TxPower in the extended header of the advertising PDU
#define ESP_BLE_GAP_SET_EXT_ADV_PROP_MASK                                 (0x7F)   // Reserved for future use
*/
esp_ble_gap_ext_adv_params_t ext_adv_params_coded = {
	.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED | ESP_BLE_GAP_SET_EXT_ADV_PROP_INCLUDE_TX_PWR,
    .interval_min = 0x50,
    .interval_max = 0x50,
    .channel_map = ADV_CHNL_ALL,
    .own_addr_type = BLE_ADDR_TYPE_RANDOM,
    .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
    .tx_power = ESP_PWR_LVL_P18,			// 送信出力 (P6,P9,P12,P15,P18)
//	.primary_phy = ESP_BLE_GAP_PHY_1M,		// Google Pixel 5 の場合はこちらでないと受信できませんでした。 
    .primary_phy = ESP_BLE_GAP_PHY_CODED,								
    .max_skip = 0,
    .secondary_phy = ESP_BLE_GAP_PHY_CODED,
    .sid =0,
    .scan_req_notif = false,
};

BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
std::string rxValue;
std::string txValue;

BLEMultiAdvertising advert(1); // max number of advertisement data 

//----- GNSS( GPS ) 設定用 ---------------------------------------------
#pragma pack(push,1)
typedef struct{
	uint8_t sync1 = 0xB5;
	uint8_t sync2 = 0x62;
	uint8_t cl = 0x06;
	uint8_t id = 0x00;
	uint16_t len = 20;	
	uint8_t port = 1;
	uint8_t rs = 0;
	uint16_t txReady = 0x0000;
	uint32_t mode = 0x000008D0;			// 8bit 1stopbit noPlity
	uint32_t baudrate = BAUDRATE;		// ボーレート
	uint16_t inProtoMaske = 0x0007;		// RTCM2,NMEA,UBX
	uint16_t outProtoMaske = 0x0003; 	// NMEA,UBX
	uint16_t flags = 0;
	uint16_t rs2 = 0;
	uint8_t ckA,ckB;
}UBX_CFG_PRT;

typedef struct {
	uint8_t sync1 = 0xB5;
	uint8_t sync2 = 0x62;
	uint8_t cl = 0x06;
	uint8_t id = 0x00;
	uint16_t len = 1;	
	uint8_t d = 1;
	uint8_t ckA = 0x08;
	uint8_t ckB = 0x22;
}UBX_CFG_PRT_POLL;

typedef struct{
	uint8_t sync1 = 0xB5;
	uint8_t sync2 = 0x62;
	uint8_t cl = 0x06;
	uint8_t id = 0x08;
	uint16_t len = 6;
	uint16_t measRate = RATE;	// 更新時間(ms)
	uint16_t navRate = 1;		// 比率
	uint16_t timeRef = 1;		// GPS time	
	uint8_t ckA,ckB;
}UBX_CFG_RATE;
#pragma pack(pop)

int set_cs(uint8_t *cmd){	// チェックサムを計算
	int i,n;
	uint16_t len;
	uint8_t ca=0;
	uint8_t cb=0;
	len = cmd[4]+cmd[5]*16;
	for(i=2; i<len+4+2; i++){
		ca = (ca + cmd[i]) & 0xFF;
		cb = (cb + ca) & 0xFF;
	}
 	cmd[i++]= ca;
 	cmd[i] = cb;
 	return len+8;	// 全体の長さを返す
}

void send_UBX(uint8_t *buf){	// GNSSに設定コマンドを送る
	int len;
	len = set_cs(buf);			// チェックサムを計算
	for(int i=0; i<len; i++){
		Serial1.write(buf[i]);
	}
	Serial1.println("");
	delay(300);
}

UBX_CFG_PRT 		cfg_prt;
UBX_CFG_PRT_POLL	cfg_prt_poll;
UBX_CFG_RATE		cfg_rate;
const  uint32_t init_speed[5] = { 115200, 57600, 38400, 19200, 9600 };

void gnss_setup(){
	// GNSSのボーレートを設定する 
	for (int i = 0; i < 5; i++){	// 現在の速度が判らないので、可能性のある速度でコマンドを送信する。どれかで通じるはず。
		Serial1.begin(init_speed[i], SERIAL_8N1, RX_PIN, TX_PIN);
		while(!Serial1){}
		send_UBX((uint8_t *)&cfg_prt);			// ボーレート設定コマンド
		send_UBX((uint8_t *)&cfg_prt_poll);		// 確認用コマンド
		Serial1.end();
		delay(300);
	}
	Serial1.begin(BAUDRATE, SERIAL_8N1, RX_PIN, TX_PIN);
	while(!Serial1){}
	send_UBX((uint8_t *)&cfg_rate);				// 更新レートを10Hz(100ms間隔)に設定
}
//-------------------------------------------------------------------------------------------------

AsyncWebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

bool wificonnected = false;
bool sended = false;
uint32_t on_time;

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
	switch(type) {
		case WStype_DISCONNECTED:				// 切断されたとき
			server.end();
			wificonnected = false;
			break;
	
		case WStype_CONNECTED:					// 接続されたとき
			wificonnected = true;
			setJson();
			webSocket.broadcastTXT(jsonTxt);	// 登録データ送信
			on_time = millis();
			break;
	
		case WStype_TEXT:						// テキストデータが受信されたとき
			decodeJson((char *)payload);
			file_write();						// SPIFFSデータ書き換え		
			break;
	
		case WStype_BIN:						// バイナリ受信したら
			break;
		case WStype_ERROR:			
		case WStype_FRAGMENT_TEXT_START:
		case WStype_FRAGMENT_BIN_START:
		case WStype_FRAGMENT:
		case WStype_FRAGMENT_FIN:
			break;
	}
}

void wifi_start(){
  WiFi.softAP(ssid,pass);
  delay(100);
  WiFi.softAPConfig(ip,ip,subnet);
  
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/index.html");
  });

  server.begin();
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
}

void wifi_stop(){
  webSocket.close();
  server.end();
  WiFi.mode(WIFI_OFF);
  WiFi.disconnect(true);
}

void setup() {
//	Serial.begin(115200);

  pinMode(BOOT_BUTTON, INPUT);
  if(!SPIFFS.begin(true)){  // SPIFFSのセットアップ
		return;
  }

  gnss_setup();	            // GNSSモジュール(BN-180)の設定

  BLEDevice::init("");
  advert.setAdvertisingParams(0, &ext_adv_params_coded);
  advert.setInstanceAddress(0, addr_coded);
  advert.setDuration(0);	
  adv_data_init();
  advert.setAdvertisingData(0, sizeof(Adv_Data), (uint8_t *)&adv);
  delay(1000); 
  advert.start();
}

bool boot_mode = false;
bool boot_btn = false;

void loop() {
	if(!boot_mode){								    // 書き換えモードチェック
		if(digitalRead(BOOT_BUTTON) == LOW){	    // Bootボタンが押されたら
			if(boot_btn == false){
				on_time = millis();				    // 最初に押された時刻を記憶
				boot_btn = true;
			}
			else{
				if((millis() - on_time) > 2000){	// BOOTボタンが2秒以上押されたら
					wifi_start();					// WiFi AP を開始する
					boot_mode = true;
					on_time = millis();
				}
			}
		}
		else{
			boot_btn = false;
		}
	}else{											// 書き込みモードなら
		webSocket.loop();

		if((millis() - on_time) > 180000){		    // 3分経過したらwifiを止める
			wifi_stop();
			boot_mode = false;
		}
	}
	
	while(Serial1.available()){						// GNSSからデータが来たら
		if(gps.encode(Serial1.read())){
			if(gps_read()){
 				advert.setAdvertisingData(0, sizeof(Adv_Data), (uint8_t *)&adv);	// 拡張アドバタイズの内容を変更する
				adv.counter++;
			}
		}
	}
}

最後にプログラムをESP32C3に焼きます。

これでリモートID発信機の出来上がりです。

電源を入れて、衛星を受信すると、GM10 Nanoの緑色のLEDが、点灯から点滅に変わります。

RID書き込みモードの使用方法

ESP32C3 の電源を入れた状態で、[BOOT]ボタンを2秒以上押すと、RID書き込みモードになります。
スマホのWiFi接続先を[my_RemoteID]にして、パスワードに[12345678]と入力します。
(スマホに名前が表示されるまで、結構時間がかかる場合がありますが、待ってれば出てくると思います。)
 機種によってはもっと質の良いWiFiに繋ぎたい見たいなメッセージが出る場合があります。このときは[許可]ではなく[拒否]を選択して下さい。

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

内容を書き換えて、[送信]ボタンを押すと書き込まれます。
3分経過すると一応自動でWiFiは閉じるようにしましたが、電源を入れ直した方が早いです。

最後にお願い

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

7
10
13

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
7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?