複数のRIDに対応しました。
OpenDroneIDアプリを参考にしました。
OpenDroneIDアプリでは、発信機の区別を何でしているのか調べたら、BLEの6バイトのMACアドレスで行っていたので、私もそうします。
なので、RID送信機を自作される場合、送信機のBLE MACアドレスはそれぞれ個別の、世界で唯一のアドレスを設定して下さい。
uint8_t addr_coded[6] = {0xC0, 0xDE, 0x52, 0x00, 0x00, 0x01}; // BLE Address (上位2bit[11] = Random Address:Static Device Address) 他の人と被らない数値を設定して下さい。
準備
前回までを、まず作って下さい。
GNSS(GPS)モジュールは取り付けても、付けなくてもどちらでも良いです。
付ければ、操縦者(受信機)の位置もマップに表示されます。
プログラム
新たに[info.html]を[data]フォルダに作ってください。
中身は以下です。
info.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" name = "viewport" content = "width=device-width, initial-scale=1">
<title>RID Receiver</title>
<style>
body {background-color:rgba(128,128,128,0.322);}
h1 {font-size: 1rem; margin: 0px; text-align: center; color:rgb(156,5,5); background-color: whitesmoke;}
h2 {font-size: 1rem; margin: 0px; color:black;}
span {color:blue;}
.pos {position:absolute; left: 0px; top:0px; width: 1000px; height: 1000px;}
a {font-size: 1rem; margin-left: 40px; color:rgb(133, 67, 255);}
</style>
</head>
<!-----------------------------------------HTML----------------------------------------->
<body>
<h1>RID受信情報</h1>
<h2>
名前: <span style="color:rgb(216, 3, 3)" id="name"></span><a id="sat"></a><br>
MAC: <span id="addr"></span><br>
RSSI: <span id="rssi">0</span><br>
Primary PHY: <span id="pPhy"></span><br>
Secondary PHY: <span id="sPhy"></span><br>
Tx Power: <span id="pw"></span><span>dBm</span><br>
Counter: <span id="cnt"></span><br>
---------------------------------<br>
製造番号: <span id="SN"></span><br>
登録記号: <span id="RN"></span><br>
機体種別: <span id="UA"></span><br>
緯度: <span id="lat"></span><br>
経度: <span id="lng"></span><br>
速度: <span id="speed"></span><br>
高度: <span id="alti"></span><br>
方角: <span id="dir"></span><br>
時刻: <span id="time"></span><br>
<div id="dist"></div>
</h2>
</body>
<!-------------------------------------JavaScript--------------------------------------->
<script>
var mac; // BLE MAC address
var R_EARTH = 6378137; // 地球の赤道半径
var RAD = Math.PI / 180; // 1°あたりのラジアン
function distance(lat1, lon1, lat2, lon2) { // 2点間の距離を求める関数
lat1 *= RAD; // 度をラジアンに変換
lon1 *= RAD;
lat2 *= RAD;
lon2 *= RAD;
var lat_c = (lat1 + lat2) / 2; // 緯度の中心値
var dx = R_EARTH * (lon2 - lon1) * Math.cos(lat_c);
var dy = R_EARTH * (lat2 - lat1);
return Math.round((Math.sqrt(dx * dx + dy * dy)).toFixed(1)); // 2点間の距離を返す
}
function InitWebSocket(){ // Websocketの初期設定
websock = new WebSocket('ws://'+window.location.hostname+':81/'); // Websocket開始
websock.onmessage = function(evt){ // データが送られて来た時の処理
JSONobj = JSON.parse(evt.data); // Json文字列を解析
if(JSONobj.addr == mac){
document.getElementById('name').innerHTML = JSONobj.name;
document.getElementById('addr').innerHTML = JSONobj.addr;
document.getElementById('rssi').innerHTML = JSONobj.rssi;
document.getElementById('cnt').innerHTML = JSONobj.cnt;
document.getElementById('SN').innerHTML = JSONobj.SN;
document.getElementById('RN').innerHTML = JSONobj.RN;
document.getElementById('UA').innerHTML = JSONobj.UA;
document.getElementById('lat').innerHTML = JSONobj.lat;
document.getElementById('lng').innerHTML = JSONobj.lng;
document.getElementById('speed').innerHTML = JSONobj.speed;
document.getElementById('alti').innerHTML = JSONobj.alti;
document.getElementById('dir').innerHTML = JSONobj.dir;
document.getElementById('time').innerHTML = JSONobj.time;
document.getElementById('pPhy').innerHTML = JSONobj.pPhy;
document.getElementById('sPhy').innerHTML = JSONobj.sPhy;
document.getElementById('pw').innerHTML = JSONobj.pw;
if(Number(JSONobj.sat) != 0){ // 補足衛星数がゼロでなかったら
document.getElementById('sat').innerHTML = JSONobj.sat + "衛星"; // 衛星数を表示する
}
let op_lng = parseFloat(JSONobj.oplng); // オペレータ位置情報
let op_lat = parseFloat(JSONobj.oplat);
if(op_lng > 0 && op_lat > 0){
let lng = parseFloat(JSONobj.lng); // RIDの位置情報
let lat = parseFloat(JSONobj.lat);
document.getElementById('dist').innerHTML = "距離: " + String(distance(op_lat, op_lng, lat, lng)) + "m"; // 2点間の距離
}else{
document.getElementById('dist').innerHTML == "";
}
}
}
}
window.onload = function() { // ページが読み込まれたら最初に実行する
InitWebSocket();
const url = new URL(window.location.href); // URLを取得
const params = url.searchParams; // URLSearchParamsオブジェクトを取得
mac = params.get("mac"); // パラメータから「MACアドレス」を取得
}
</script>
</html>
index.htmlを書き換えます。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" name = "viewport" content = "width=device-width, initial-scale=1">
<title>RID Receiver</title>
<style>
body {background-color:rgba(128,128,128,0.322);}
h1 {font-size: 0.7rem; width: 100%; height: 26px; margin: 0px; vertical-align:middle; padding-top:0px; text-align:center; color:rgb(255, 255, 255); background-color: rgb(60, 60, 255);}
a {vertical-align:middle; margin: 0px; padding-top:0px;}
button {font-size: 0.7rem; float: right; margin-right:3px; margin-top: 0px; padding-top: 0px; background-color: rgba(255,255,255,0.5);}
.map {position:absolute; width: 100%; margin: 0px; left: 0px; top:0px; z-index: -1;}
.mark {position:absolute; margin: 0px; padding: 0px; visibility:hidden;}
table {font-size: 0.9rem; width: 100%; margin-top:0px;position:relative;}
td {background-color: rgb(160, 200, 255);}
</style>
</head>
<!-----------------------------------------HTML----------------------------------------->
<body>
<h1>
<a id="title">0 機</a><a id="sat" style="margin-left: 80px;"></a><button id="clear">クリア</button>
</h1>
<div id="area" style="position:relative; margin-top:0px; width:100%;">
<img src="map0" id="map0" alt="" class="map" style="visibility: hidden;">
<img src="map1" id="map1" alt="" class="map" style="visibility: hidden;">
<img src="map2" id="map2" alt="" class="map" style="visibility: hidden;">
<img src="map" id="map" alt="" class="map" style="visibility: visible;">
<canvas id="cvs" class="map" width="1000px" height="1000px"></canvas>
<img src="opmark" id="opmark" alt="" class="mark">
<img src="mark" id="mark" alt="" class="mark">
</div>
<table id="tbl">
<tbody>
</tbody>
</table>
</body>
<!-------------------------------------JavaScript--------------------------------------->
<script>
var w ,h; // マップの幅,高さ
var cv_top, cv_left, cv_bottom;
var mk_scale = 1; // マークのスケール
var mk_top, mk_left; // マークの左座標の位置
var opmk_top, opmk_left; // オペレータマーク
var lng0,lat0,lng1,lat1;
var curMap = 0;
var bles = []; // 機体データ配列
var cnt = 0; // 表示切替カウンター
const area = document.getElementById('area'); // マップエリア
const title = document.getElementById('title'); // 機体数
const cvs = document.querySelector('#cvs'); // キャンバス
const ctx = document.getElementById('cvs').getContext('2d'); // キャンバスのコンテキスト
const mk = document.getElementById('mark'); // マークのオブジェクト
const opmk = document.getElementById('opmark'); // オペレータマークのオブジェクト
const sat = document.getElementById('sat'); // オペレータの衛星数
const tbl = document.getElementById('tbl'); // 機体テーブル
const btn = document.getElementById('clear'); // クリアボタン
btn.addEventListener('click', function(){clear()},false); // クリアボタンが押された時の処理先を指定
var maps = [
{
obj: document.getElementById('map0'), // 自宅周辺の地図
lat0: 30.000000, // <------ ここを書き換えてください
lng0: 100.000000, // <------ ここを書き換えてください
lat1: 30.100000, // <------ ここを書き換えてください
lng1: 100.100000 // <------ ここを書き換えてください
},
{
obj: document.getElementById('map1'), // 良く行く場所の地図
lat0: 31.000000, // <------ ここを書き換えてください
lng0: 101.000000, // <------ ここを書き換えてください
lat1: 31.100000, // <------ ここを書き換えてください
lng1: 101.100000 // <------ ここを書き換えてください
},
{
obj: document.getElementById('map2'), // 遠征先の地図
lat0: 32.000000, // <------ ここを書き換えてください
lng0: 102.000000, // <------ ここを書き換えてください
lat1: 32.100000, // <------ ここを書き換えてください
lng1: 102.100000 // <------ ここを書き換えてください
},
{
obj: document.getElementById('map'), // 日本全体の地図(↑のどれにも当てはまら無い場合に表示する。これを一番最後に入れて下さい。)
lat0: 23.584126,
lng0: 122.058105,
lat1: 45.874712,
lng1: 149.523926
}
];
const clear = function(){ // クリア処理
ctx.clearRect(0, 0, w, h); // キャンバスのラインを消す
var n = tbl.rows.length; // 表の行数を取得
for(let i=0; i<n; i++){ // マークを削除
let elm = document.getElementById("mark"+i);
elm.remove();
}
while (n>0) { // テーブルの行を削除
tbl.deleteRow(n - 1); // 末尾行を削除
n--;
}
bles.length = 0; // 配列の中身を全消し
title.textContent = 0 + " 機";
}
function add_mark(no){ // マークを追加
let mark = document.createElement("img");
mark.src = "mark";
mark.id = "mark"+ no;
mark.alt = "";
mark.style.position = "absolute";
mark.style.width = Math.round(mark.width * mk_scale) + 'px';
mark.style.height = Math.round(mark.height * mk_scale) + 'px';
mark.style.zIndex = "1";
area.appendChild(mark);
return mark;
}
const func_click = function(n){ // 機種名を押された時の処理
var row = tbl.rows.length;
for (let i=0; i < row; i++){
tbl.rows[i].cells[0].style.backgroundColor = "rgb(160, 200, 255)";
bles[i].mark.style.opacity = 0.7;
}
tbl.rows[n].cells[0].style.backgroundColor = "rgb(130, 130, 255)";
bles[n].mark.style.opacity = 1;
}
const func_link = function(addr){ // 情報ボタンを押された時の処理
location.href="./info.html?mac=" + addr;
}
function add_table(bl){ // テーブルに追加
let n = tbl.rows.length;
let tr = document.createElement("tr");
let td1 = document.createElement("td");
let td2 = document.createElement("td");
let bt2 = document.createElement("button");
td1.textContent = bl.RN + " " + bl.rssi + "dBm";
td1.addEventListener('click', function(){func_click(n)},false);
bt2.textContent = "情報";
td2.appendChild(bt2);
td2.addEventListener('click', function(){func_link(bles[n].addr)},false);
tr.appendChild(td1);
tr.appendChild(td2);
tbl.appendChild(tr);
}
function add_ble(bl){ // BLEを追加する
let n = bles.push(bl) - 1; // 配列に追加
add_table(bl); // テーブルに追加
bl.mark = add_mark(n); // マークを追加
}
function info(obj){
let n = obj.parentNode.rowIndex; // 押された行番号を得る
location.href = "./info.html?mac="+ bles[n].addr;
}
function drawLine(bl,x,y){ // 移動経過のラインを描画
if(bl.x0 != 0){
ctx.beginPath();
ctx.moveTo(bl.x0, bl.y0);
ctx.lineTo(x, y);
ctx.strokeStyle = '#FF0000'; // ラインの色
ctx.lineWidth = 1; // ラインの幅
ctx.stroke();
}
bl.x0 = x;
bl.y0 = y;
}
function setMap(n){ // マップ表示を切り替える
for(let i=0; i < maps.length; i++){
maps[i].obj.style.visibility = "hidden";
}
maps[n].obj.style.visibility = "visible";
lng0 = maps[n].lng0;
lat0 = maps[n].lat0;
lng1 = maps[n].lng1;
lat1 = maps[n].lat1;
curMap = n;
ctx.clearRect(0, 0, w, h); // キャンバスのラインを消す
x0 = 0;
}
function mapCheck(lng,lat){ // 経度、緯度から表示するマップを切り替える
let n = maps.findIndex(e => e.lng0 <= lng && e.lat0 <= lat && lng <= e.lng1 && lat <= e.lat1 )
if(n !== -1){
if( n != curMap){
setMap(n);
}
}
}
function culcXY(lng,lat){ // 経度緯度をxy座標に変換
let obj ={
x : Math.round( w * (lng - lng0) / (lng1 - lng0)),
y : h - Math.round( h * (lat - lat0) / (lat1 - lat0))
}
return obj;
}
function InitWebSocket(){ // Websocketの初期設定
websock = new WebSocket('ws://'+window.location.hostname+':81/'); // Websocket開始
websock.onmessage = function(evt){ // データが送られて来た時の処理
var add = false;
var bl;
JSONobj = JSON.parse(evt.data); // 受信データを解析
if(JSONobj.addr != "00:00:00:00:00:00"){
bl = bles.find(e=> e.addr === JSONobj.addr); // 既に配列データに格納されているか?
if(bl == undefined){ // 無ければ新たに作る
bl = new Object();
add = true;
}
bl.addr = JSONobj.addr; // データを更新する
bl.sat = JSONobj.sat;
bl.rssi = JSONobj.rssi;
bl.SN = JSONobj.SN;
bl.RN = JSONobj.RN;
bl.lat = JSONobj.lat;
bl.lng = JSONobj.lng;
if(add) add_ble(bl); // 配列に加える
}
var ct = bles.length; // 受信している機体の個数
let op_lng = parseFloat(JSONobj.oplng); // オペレータ位置情報
let op_lat = parseFloat(JSONobj.oplat);
let ox = oy = 0;
if(op_lng > 0 && op_lat > 0){ // オペレータ位置情報がある場合
if(ct == 0) mapCheck(op_lng, op_lat); // RID機体がゼロの場合、受信機のマップにする。
let oxy = culcXY(op_lng, op_lat); // 経度緯度をxy座標に変換
ox = oxy.x;
oy = oxy.y;
if(curMap == maps.length-1){
oy += Math.round(36 * mk_scale); // 日本地図では何故か縦づれするので補正
}
opmk.style.left = ox - opmk_left - 8 + 'px'; // オペレータマークの位置を変更(マークの左上角座標)
opmk.style.top = oy - opmk_top - 6 + 'px';
opmk.style.visibility = "visible";
sat.textContent = "受信機 " + JSONobj.opsat + " 衛星";
}else{
opmk.style.visibility = "hidden";
sat.textContent = "";
}
title.textContent = ct + " 機"; // 表示
for(let i=0; i < ct; i++){ // 機体の個数分のマーク表示
bl = bles[i];
let lng = parseFloat(bl.lng); // RID 位置情報
let lat = parseFloat(bl.lat);
mapCheck(lng,lat); // 現在地に合わせて、マップを変更する
let xy = culcXY(lng, lat); // 経度緯度をxy座標に変換
let x = xy.x;
let y = xy.y;
if(curMap == maps.length-1){
y += Math.round(34 * mk_scale); // 日本地図では何故かづれするので補正
x -= Math.round(6 * mk_scale);
}
bl.mark.style.left = x - mk_left - 8 + 'px'; // マークの位置を変更(マークの左上角座標)
bl.mark.style.top = y - mk_top - 6 + 'px';
drawLine(bl, x, y); // 軌跡を描画
cnt++; // 定期的に機体情報の表示項目を変える
let str = bl.RN; // 登録記号
if(cnt > 30 && cnt < 60){
str = bl.SN; // 製造番号
}else if(cnt > 60){
cnt = 0;
}
str += " " + bl.rssi + "dBm ";
if(Number(bl.sat) != 0){
str += bl.sat + "衛星";
}
tbl.rows[i].cells[0].textContent = str; // 機体情報を表示
}
}
}
function initScreen(){ // スマホの画面に合わせてサイズを変更する
w = window.innerWidth; // 画面の幅をマップのサイズにする
h = w;
tbl.style.top = h - 17 + 'px';
area.height = h;
cvs.setAttribute("width", w);
cvs.setAttribute("height", h);
mk_scale = w / 1000;
mk_left = Math.round(mk.width * mk_scale/2);
mk_top = Math.round(mk.height * mk_scale);
opmk_left = Math.round(opmk.width * mk_scale/2);
opmk_top = Math.round(opmk.height * mk_scale);
opmk.style.width = opmk_left * 2 + 'px';
opmk.style.height = opmk_top + 'px';
}
window.onload = function() { // ページが読み込まれたら最初に実行する
initScreen(); // 画面の大きさ調整
InitWebSocket(); // ウェブソケット初期化
setMap(0); // 最初に表示するマップ番号を指定
}
window.addEventListener('DOMContentLoaded', () => {
cv_top = Math.round(cvs.getBoundingClientRect().top + window.scrollY);
cv_left = Math.round(cvs.getBoundingClientRect().left + window.scrollY);
cv_bottom = Math.round(cvs.getBoundingClientRect().bottom + window.scrollY);
})
</script>
</html>
main.cppを書き換えます。
main.cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <WebSocketsServer.h>
#include <SPIFFS.h>
#include <FS.h>
#include <TinyGPSPlus.h>
const char *ssid="RID_Receiver";
const char *pass="12345678"; // パスワード(8Byte以上)
const IPAddress ip(192,168,5,1); // IPアドレス
const IPAddress subnet(255,255,255,0); // サブネットマスク
// ADタイプ
#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 // 操縦者ID [文字列]
#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
#pragma pack(push,1) // データを1バイト単位に詰めて配置
typedef struct{
uint8_t type; // Location(MSG_TYPE_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; // 垂直、水平速度精度
uint8_t B_S_Accuracy; // 気圧、速度精度
uint16_t timestamp; // 現在時間の分以下小数点第1位までの秒数X10
uint8_t T_Accuracy; // 時間精度
uint8_t resv3;
} location;
typedef struct{
uint8_t type; // 認証情報
uint8_t Auth_Type; // Authentication Message [認証情報]
uint8_t page_count; // Page0
uint8_t Length; // headからのサイズ
time_t timestamp_auth; // 現在時刻(2019.1.1からの秒数)
uint8_t auth_head; // ヘッダ
char auth_data[16]; // 認証データ
}AUTH;
#pragma pack(pop)
TinyGPSPlus gps;
AsyncWebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
uint32_t scanTime = 100; //In 10ms (1000ms)
BLEScan* pBLEScan;
String phy[3] ={"1M PHY","2M PHY","Coded PHY"};
String ua[16] ={"なし","飛行機","ヘリコプター","ジャイロプレーン","ハイブリッドリフト","羽ばたき機","グライダー","カイト(凧)","自由気球","係留気球","飛行船","パラシュート","ロケット"," テザー式動力航空機","地上障害物","その他"};
uint8_t msg_size = 25;
location *loc;
AUTH *auth;
uint8_t counter, ua_type;
char name[30],serNo[23],regNo[23],addr[18],sat[4];
char s_lat[20],s_lng[20],s_time[22],s_speed[20],s_alti[20], s_dir[6],op_lat[20],op_lng[20],op_sat[10];
int8_t rssi;
uint8_t primary_phy, secondary_phy, tx_power;
String jsonTxt;
bool dataReady = false;
uint16_t ss=0;
void setJson(){
jsonTxt ="{\"addr\":\"" + String(addr) + "\",";
jsonTxt += "\"name\":\"" + String(name) + "\",";
jsonTxt += "\"sat\":\"" + String(sat) + "\",";
jsonTxt += "\"rssi\":\"" + String(rssi) + "\",";
jsonTxt += "\"cnt\":\"" + String(counter) + "\",";
jsonTxt += "\"SN\":\"" + String(serNo) + "\",";
jsonTxt += "\"RN\":\"" + String(regNo) + "\",";
jsonTxt += "\"UA\":\"" + ua[ua_type] + "\",";
jsonTxt += "\"lat\":\"" + String(s_lat) + "\",";
jsonTxt += "\"lng\":\"" + String(s_lng) + "\",";
jsonTxt += "\"speed\":\"" + String(s_speed) + "\",";
jsonTxt += "\"alti\":\"" + String(s_alti) + "\",";
jsonTxt += "\"dir\":\"" + String(s_dir) + "\",";
jsonTxt += "\"time\":\"" + String(s_time) + "\",";
jsonTxt += "\"pPhy\":\"" + phy[primary_phy] + "\",";
jsonTxt += "\"sPhy\":\"" + phy[secondary_phy] +"\",";
jsonTxt += "\"pw\":\"" + String(tx_power) + "\",";
jsonTxt += "\"opsat\":\"" + String(op_sat) + "\",";
jsonTxt += "\"oplat\":\"" + String(op_lat) + "\",";
jsonTxt += "\"oplng\":\"" + String(op_lng) + "\"}";
dataReady = true;
}
time_t tm2019; // 1900年から2019年までの秒数
void get_tm2019(){
struct tm stm;
stm.tm_year = 2019 - 1900;
stm.tm_mon = 0;
stm.tm_mday = 1;
stm.tm_hour = 0;
stm.tm_min = 0;
stm.tm_sec = 0;
tm2019 = mktime( &stm );
}
void gps_read(void){ // GNSSのデータを読む
float lat = 0;
float lng = 0;
if(gps.satellites.isValid()){ // 補足衛星数
sprintf(op_sat,"%d", gps.satellites.value());
}
if (gps.location.isValid()){ // 緯度経度
lat = gps.location.lat();
lng = gps.location.lng();
}
sprintf(op_lat,"%10.7f",lat);
sprintf(op_lng,"%10.7f",lng);
if(lat > 0) setJson();
}
uint8_t *decode_msg(uint8_t *data){ // メーッセージデータをデコードする
uint8_t *adr = data;
uint8_t type,size,n;
uint8_t id_type,status,dir;
float lat,lng,alti;
uint16_t h,m,s;
uint16_t speed;
time_t tim;
struct tm t;
type = data[0] & 0xF0;
switch(type){
case MSG_TYPE_PACK: // パケットデータ
msg_size = data[1];
n = data[2];
adr = &data[3];
for(int i=0;i<n;i++){
adr = decode_msg(adr);
}
break;
case MSG_TYPE_BASIC_ID:
id_type = data[1] & 0xF0;
if(id_type == ID_TYPE_SerialNo){ // 製造番号
memcpy(serNo, &data[2], 20);
ua_type = data[1] & 0x0F; // 機体種別
adr += msg_size;
}
if(id_type == ID_TYPE_ASSIGED_REG){ // 登録記号
memcpy(regNo, &data[2], 20);
adr += msg_size;
}
break;
case MSG_TYPE_LOCATION: // 位置情報
loc = (location *)&data[0];
status = loc->status;
lat = (float)loc->lat/10000000; // 緯度
lng = (float)loc->lng/10000000; // 経度
sprintf(s_lat,"%10.7f",lat);
sprintf(s_lng,"%10.7f",lng);
h = loc->timestamp%36000; // 時刻
m = h/600;
s = (h%600)/10;
ss = h%10;
s_time[20] = '0' + ss;
speed = loc->speed; // 速度
if(speed<254){
if(status & LOC_FLAG_SM){
speed = speed * 0.75 + 255/4;
}else{
speed /= 4;
}
}
sprintf(s_speed,"%4dm/s",speed);
alti = loc->Geodetic_Altitude; // 高度
alti = alti/2-1000;
sprintf(s_alti,"%7.1fm",alti);
dir = loc->dir; // 方向
if(status & LOC_FLAG_EW){
dir += 180;
}
sprintf(s_dir,"%3d°", dir);
adr += msg_size;
break;
case MSG_TYPE_AUTH: // 認証情報
auth = (AUTH *)&data[0];
tim = auth->timestamp_auth + tm2019;
tim += 32400; // 9*60*60 (秒で9時間進める。日本時間用)
t = *localtime(&tim);
sprintf(s_time,"%04d/%02d/%02d %02d:%02d:%02d.%1d", t.tm_year + 1900, t.tm_mon +1 , t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, ss);
adr += msg_size;
break;
default:
adr += msg_size;
break;
}
return adr;
}
void decode_data(uint8_t *data, uint8_t size){ // ADVデータをデコードする
uint8_t *adr, len, type;
adr = data;
while(adr < data+size){
len = adr[0];
if(len==0)break;
type = adr[1];
switch(type){
case AD_TYPE_CMP_LOCAL_NAME: // 名前データ
memcpy(name, &adr[2], 28);
break;
case AD_TYPE_SHT_LOCAL_NAME: // 短縮名データ
memcpy(sat, &adr[2], 3); // 衛星補足数
break;
case AD_TYPE_SERVICE_DATA: // サービスデータ
counter = adr[5];
decode_msg(&adr[6]); // メッセージをデコードする
break;
default:
break;
}
adr += len+1;
}
}
/**
* @brief extend adv report parameters
*/
//typedef struct {
// esp_ble_gap_adv_type_t event_type; /*!< extend advertising type */
// uint8_t addr_type; /*!< extend advertising address type */
// esp_bd_addr_t addr; /*!< extend advertising address */
// esp_ble_gap_pri_phy_t primary_phy; /*!< extend advertising primary phy */
// esp_ble_gap_phy_t secondly_phy; /*!< extend advertising secondary phy */
// uint8_t sid; /*!< extend advertising sid */
// uint8_t tx_power; /*!< extend advertising tx power */
// int8_t rssi; /*!< extend advertising rssi */
// uint16_t per_adv_interval; /*!< periodic advertising interval */
// uint8_t dir_addr_type; /*!< direct address type */
// esp_bd_addr_t dir_addr; /*!< direct address */
// esp_ble_gap_ext_adv_data_status_t data_status; /*!< data type */
// uint8_t adv_data_len; /*!< extend advertising data length */
// uint8_t adv_data[251]; /*!< extend advertising data */
//} esp_ble_gap_ext_adv_reprot_t;
// BLEがアドバタイジングデータを受信したとき呼ばれる
class MyBLEExtAdvertisingCallbacks: public BLEExtAdvertisingCallbacks {
void onResult(esp_ble_gap_ext_adv_reprot_t report) {
if(report.event_type & ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY){
// Serial.println("BLE4.2");
}else{ // BLE 5.x の時
esp_bd_addr_t &a = report.addr; // BLE Address[6Bytes]
sprintf(addr,"%02X:%02X:%02X:%02X:%02X:%02X",a[0],a[1],a[2],a[3],a[4],a[5]);
rssi = report.rssi;
primary_phy = report.primary_phy -1;
secondary_phy = report.secondly_phy -1;
tx_power = report.tx_power;
decode_data(report.adv_data, report.adv_data_len);
setJson();
}
}
};
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
break;
case WStype_CONNECTED:
break;
case WStype_TEXT:
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;
}
}
//----- GNSS( GPS ) 設定用 ---------------------------------------------
#define BAUDRATE 115200 // GNSS ボーレート
#define RATE 100 // GNSS 更新レート 100ms(10Hz)
#define RX_PIN 20
#define TX_PIN 21
#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){
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間隔)に設定
}
//-------------------------------------------------------------------------------------------
void setup() {
// Serial.begin(115200);
gnss_setup(); // GNSSモジュールの初期設定
if(!SPIFFS.begin(true)){ // SPIFFSのセットアップ
return;
}
get_tm2019(); // 2019年までの秒数を計算しておく
sprintf(addr,"%02X:%02X:%02X:%02X:%02X:%02X",0,0,0,0,0,0);
BLEDevice::init("");
pBLEScan = BLEDevice::getScan();
pBLEScan->setExtendedScanCallback(new MyBLEExtAdvertisingCallbacks()); // BLE コールバックルーチンを指定
pBLEScan->setExtScanParams();
delay(1000);
pBLEScan->startExtScan(scanTime, 1); // scan duration in n * 10ms, period - repeat after n seconds (period >= duration)
WiFi.softAP(ssid,pass);
WiFi.softAPConfig(ip,ip,subnet);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html");
});
server.on("/info.html", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/info.html");
});
server.on("/map", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/map.jpg", "image/jpg");
});
server.on("/map0", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/map0.jpg", "image/jpg");
});
server.on("/map1", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/map1.jpg", "image/jpg");
});
server.on("/map2", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/map2.jpg", "image/jpg");
});
server.on("/mark", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/mark.png", "image/png");
});
server.on("/opmark", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/opmark.png", "image/png");
});
server.begin();
webSocket.begin();
webSocket.onEvent(webSocketEvent);
}
void loop() {
webSocket.loop();
while(Serial1.available()){ // UARTにGNSSのデータが来てたら全て読み込む
if(gps.encode(Serial1.read())){
gps_read();
}
}
if(dataReady){
webSocket.broadcastTXT(jsonTxt); // WebSocketでデータ送信
dataReady = false;
}
}
以上です。