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]の中身は以下のようにして下さい。
追加ライブラリーやシリアルモニタの速度、パーテッションの指定をしています。
(プログラム領域の右上のアイコンをクリックすると全コピーできます。)
[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]にします。中身は以下です。
<!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]の中身に下記のコードをコピペして下さい。
#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は閉じるようにしましたが、電源を入れ直した方が早いです。
最後にお願い
これを作成又は使用したことにより、何らかの問題が発生したとしても、私は一切責任を負いませんので、あくまで自己責任でお願い致します。