Integrate WFB settings to dashboard

This commit is contained in:
Alex 2024-10-20 17:18:42 -03:00
parent aa97e869d5
commit 77659e7fa8
7 changed files with 518 additions and 10 deletions

View File

@ -51,6 +51,7 @@ install:
@$(SUDO) apt install -y nload
@$(SUDO) apt install -y htop
@$(SUDO) apt install -y picocom
@$(SUDO) apt install -y sshpass
# install mavlink-router
@rm -rf ~/tmp/mavlink-router-source

View File

@ -18,13 +18,13 @@ body.login-pf {
}
#brand:before {
content: "EchoPilot AI";
content: "Spiri Mu2";
}
#index-brand {
content: "EchoPilot AI";
content: "Spiri Mu2";
}
#index-brand:before {
content: "EchoPilot AI";
content: "Spiri Mu2";
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -6,6 +6,11 @@
"label": "MAVLink Telemtry",
"path": "general.html",
"order": 1
},
"SpiriLink": {
"label": "SpiriLink",
"path": "spirilink.html",
"order": 2
}
}
}

245
ui/general/spirilink.html Normal file
View File

@ -0,0 +1,245 @@
<!DOCTYPE html>
<html>
<head>
<title>SpiriLink Configuration</title>
<meta charset="utf-8">
<link href="../base1/bootstrap.min.css" type="text/css" rel="stylesheet">
<link href="../base1/style.css" type="text/css" rel="stylesheet">
<script src="../base1/cockpit.js"></script>
<script src="../base1/jquery-3.7.1.min.js"></script>
</head>
<body>
<div class="container">
<div class="row mt-4">
<div class="col-12">
<h2 class="fw-bold">SpiriLink Configuration</h2>
<p>Configuration File: <i><span
id='file_location'></span></i></p>
<p>SpiriLink provides Mavlink, RTSP video and a UDP tunnel over a modified 8812EU wireless card to provide a long distance, high speed wireless link. Edit the configuration settings below.</p>
</div>
</div>
<!-- WiFi Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="settings_ethernet.svg" class="me-2"> WiFi Configuration
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<label for="wifiChannel" class="form-label">WiFi Channel</label>
<select id="wifiChannel" class="form-select">
<!-- 2.4 GHz Channels -->
<option value="1">1 (2.412 GHz)</option>
<option value="2">2 (2.417 GHz)</option>
<option value="3">3 (2.422 GHz)</option>
<option value="4">4 (2.427 GHz)</option>
<option value="5">5 (2.432 GHz)</option>
<option value="6">6 (2.437 GHz)</option>
<option value="7">7 (2.442 GHz)</option>
<option value="8">8 (2.447 GHz)</option>
<option value="9">9 (2.452 GHz)</option>
<option value="10">10 (2.457 GHz)</option>
<option value="11">11 (2.462 GHz)</option>
<option value="12">12 (2.467 GHz)</option>
<option value="13">13 (2.472 GHz)</option>
<option value="14">14 (2.484 GHz, Japan only)</option>
<!-- 5 GHz Channels -->
<option value="36">36 (5.180 GHz)</option>
<option value="40">40 (5.200 GHz)</option>
<option value="44">44 (5.220 GHz)</option>
<option value="48">48 (5.240 GHz)</option>
<option value="52">52 (5.260 GHz)</option>
<option value="56">56 (5.280 GHz)</option>
<option value="60">60 (5.300 GHz)</option>
<option value="64">64 (5.320 GHz)</option>
<option value="100">100 (5.500 GHz)</option>
<option value="104">104 (5.520 GHz)</option>
<option value="108">108 (5.540 GHz)</option>
<option value="112">112 (5.560 GHz)</option>
<option value="116">116 (5.580 GHz)</option>
<option value="120">120 (5.600 GHz)</option>
<option value="124">124 (5.620 GHz)</option>
<option value="128">128 (5.640 GHz)</option>
<option value="132">132 (5.660 GHz)</option>
<option value="136">136 (5.680 GHz)</option>
<option value="140">140 (5.700 GHz)</option>
<option value="149">149 (5.745 GHz)</option>
<option value="153">153 (5.765 GHz)</option>
<option value="157">157 (5.785 GHz)</option>
<option value="161">161 (5.805 GHz)</option>
<option value="165">165 (5.825 GHz)</option>
</select>
</div>
<div class="col-md-4">
<label for="wifiRegion" class="form-label">WiFi Region</label>
<select id="wifiRegion" class="form-select">
<option value="BO">BO (Max TX)</option>
<option value="US">US (United States)</option>
<option value="EU">EU (Europe)</option>
<option value="JP">JP (Japan)</option>
<option value="AU">AU (Australia, Max TX)</option>
<option value="CA">CA (Canada)</option>
<option value="CN">CN (China)</option>
<option value="IN">IN (India)</option>
<option value="ZA">ZA (South Africa)</option>
<option value="KR">KR (South Korea)</option>
</select>
</div>
<div class="col-md-4">
<label for="wifiTxPower" class="form-label">WiFi Tx Power (dBm * 100)</label>
<input type="number" id="wifiTxPower" class="form-control"
placeholder="e.g., 3000 for 30dBm">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Temperature Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="settings_ethernet.svg" class="me-2"> Temperature Configuration
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="tempInterval" class="form-label">Measurement Interval (s)</label>
<input type="number" id="tempInterval" class="form-control" value="10">
</div>
<div class="col-md-6">
<label for="tempWarning" class="form-label">Overheat Warning (°C)</label>
<input type="number" id="tempWarning" class="form-control" value="60">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Drone MAVLink Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="settings_ethernet.svg" class="me-2"> Drone MAVLink Configuration
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="droneMavlinkFwmark" class="form-label">FWMark</label>
<input type="number" id="droneMavlinkFwmark" class="form-control" value="10">
</div>
<div class="col-md-3">
<label for="droneMavlinkType" class="form-label">Connection Type</label>
<select id="droneMavlinkType" class="form-select">
<option value="listen">Listen</option>
<option value="connect">Connect</option>
</select>
</div>
<div class="col-md-3">
<label for="droneMavlinkIP" class="form-label">IP Address</label>
<input type="text" id="droneMavlinkIP" class="form-control" value="0.0.0.0">
</div>
<div class="col-md-3">
<label for="droneMavlinkPort" class="form-label">Port</label>
<input type="number" id="droneMavlinkPort" class="form-control" value="14550">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Drone Video Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="settings_ethernet.svg" class="me-2"> Drone Video Configuration
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="droneVideoFwmark" class="form-label">FWMark</label>
<input type="number" id="droneVideoFwmark" class="form-control" value="20">
</div>
<div class="col-md-3">
<label for="droneVideoType" class="form-label">Connection Type</label>
<select id="droneVideoType" class="form-select">
<option value="listen">Listen</option>
<option value="connect">Connect</option>
</select>
</div>
<div class="col-md-3">
<label for="droneVideoIP" class="form-label">IP Address</label>
<input type="text" id="droneVideoIP" class="form-control" value="0.0.0.0">
</div>
<div class="col-md-3">
<label for="droneVideoPort" class="form-label">Port</label>
<input type="number" id="droneVideoPort" class="form-control" value="5602">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- MAVLink Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="settings_ethernet.svg" class="me-2"> MAVLink Settings
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="injectRssi" class="form-label">Inject RSSI</label>
<input type="checkbox" id="injectRssi" class="form-check-input" checked>
</div>
<div class="col-md-3">
<label for="mavlinkSysId" class="form-label">System ID</label>
<input type="number" id="mavlinkSysId" class="form-control" value="3">
</div>
<div class="col-md-3">
<label for="mavlinkCompId" class="form-label">Component ID</label>
<input type="number" id="mavlinkCompId" class="form-control" value="68">
</div>
<div class="col-md-3">
<label for="mavlinkTcpPort" class="form-label">MAVLink TCP Port</label>
<input type="number" id="mavlinkTcpPort" class="form-control"
placeholder="None if left blank">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Save Button -->
<div class="row mt-4 mb-5">
<div class="col-6">
<button class="btn btn-primary" id="save">Save/Update</button>
<span id="result" class="ms-3"></span>
</div>
<div class="col-6">
<button id="applyEncryptionFile" class="btn btn-primary">Apply Encryption File from Base Station</button>
</div>
</div>
<pre id="output"></pre>
</div>
<script src="spirilink.js"></script>
</body>
</html>

249
ui/general/spirilink.js Normal file
View File

@ -0,0 +1,249 @@
const scriptLocation = "/usr/local/echopilot/";
const confLocation = "/etc/wifibroadcast.cfg";
const encryptionFileLocation = "/etc/drone.key"; // Destination for encryption key
// Elements
const version = document.getElementById("version");
const file_location = document.getElementById("file_location");
const wifiChannel = document.getElementById("wifiChannel");
const wifiRegion = document.getElementById("wifiRegion");
const wifiTxPower = document.getElementById("wifiTxPower");
const tempInterval = document.getElementById("tempInterval");
const tempWarning = document.getElementById("tempWarning");
const droneMavlinkFwmark = document.getElementById("droneMavlinkFwmark");
const droneMavlinkType = document.getElementById("droneMavlinkType");
const droneMavlinkIP = document.getElementById("droneMavlinkIP");
const droneMavlinkPort = document.getElementById("droneMavlinkPort");
const droneVideoFwmark = document.getElementById("droneVideoFwmark");
const droneVideoType = document.getElementById("droneVideoType");
const droneVideoIP = document.getElementById("droneVideoIP");
const droneVideoPort = document.getElementById("droneVideoPort");
const injectRssi = document.getElementById("injectRssi");
const mavlinkSysId = document.getElementById("mavlinkSysId");
const mavlinkCompId = document.getElementById("mavlinkCompId");
const mavlinkTcpPort = document.getElementById("mavlinkTcpPort");
// Load initial settings
document.onload = InitPage();
// Save file button
document.getElementById("save").addEventListener("click", SaveSettings);
// Apply encryption file button
document.getElementById("applyEncryptionFile").addEventListener("click", ApplyEncryptionFile);
// Function to initialize the page
function InitPage() {
cockpit.script(scriptLocation + "cockpitScript.sh -v")
file_location.innerHTML = confLocation;
cockpit.file(confLocation)
.read()
.then((content) => {
console.log("Configuration file content:", content);
SuccessReadFile(content);
})
.catch(error => {
console.error("Failed to read configuration file:", error);
FailureReadFile(error);
});
}
// Load configuration values from the configuration file
function SuccessReadFile(content) {
try {
// WiFi Configuration
wifiChannel.value = getValueByKey(content, "common", "wifi_channel");
console.log("wifi_channel:", wifiChannel.value);
// Remove surrounding quotes from wifi_region value if present
wifiRegion.value = getValueByKey(content, "common", "wifi_region").replace(/^['"]|['"]$/g, "");
console.log("wifi_region:", wifiRegion.value);
// Handle "None" value for wifi_txpower
const txPowerValue = getValueByKey(content, "common", "wifi_txpower");
wifiTxPower.value = txPowerValue === "None" ? "" : txPowerValue;
console.log("wifi_txpower:", wifiTxPower.value);
tempInterval.value = getValueByKey(content, "common", "temp_measurement_interval");
console.log("temp_measurement_interval:", tempInterval.value);
tempWarning.value = getValueByKey(content, "common", "temp_overheat_warning");
console.log("temp_overheat_warning:", tempWarning.value);
// Drone MAVLink Configuration
droneMavlinkFwmark.value = getValueByKey(content, "drone_mavlink", "fwmark");
console.log("drone_mavlink_fwmark:", droneMavlinkFwmark.value);
const droneMavlinkPeer = getValueByKey(content, "drone_mavlink", "peer");
console.log("drone_mavlink_peer:", droneMavlinkPeer);
if (droneMavlinkPeer) {
parsePeer(droneMavlinkPeer, droneMavlinkType, droneMavlinkIP, droneMavlinkPort);
}
// Drone Video Configuration
droneVideoFwmark.value = getValueByKey(content, "drone_video", "fwmark");
console.log("drone_video_fwmark:", droneVideoFwmark.value);
const droneVideoPeer = getValueByKey(content, "drone_video", "peer");
console.log("drone_video_peer:", droneVideoPeer);
if (droneVideoPeer) {
parsePeer(droneVideoPeer, droneVideoType, droneVideoIP, droneVideoPort);
}
// MAVLink Configuration
injectRssi.checked = getValueByKey(content, "mavlink", "inject_rssi") === "True";
console.log("inject_rssi:", injectRssi.checked);
mavlinkSysId.value = getValueByKey(content, "mavlink", "mavlink_sys_id");
console.log("mavlink_sys_id:", mavlinkSysId.value);
mavlinkCompId.value = getValueByKey(content, "mavlink", "mavlink_comp_id");
console.log("mavlink_comp_id:", mavlinkCompId.value);
const tcpPortValue = getValueByKey(content, "mavlink", "mavlink_tcp_port");
mavlinkTcpPort.value = tcpPortValue === "None" ? "" : tcpPortValue;
console.log("mavlink_tcp_port:", mavlinkTcpPort.value);
} catch (e) {
console.error("Error parsing configuration file:", e);
FailureReadFile(e);
}
}
// Save configuration values to the configuration file
function SaveSettings() {
try {
cockpit.file(confLocation)
.read()
.then((content) => {
// WiFi Configuration
content = setValueByKey(content, "[common]", "wifi_channel", wifiChannel.value);
content = setValueByKey(content, "[common]", "wifi_region", wifiRegion.value);
content = setValueByKey(content, "[common]", "wifi_txpower", wifiTxPower.value === "" ? "None" : wifiTxPower.value);
content = setValueByKey(content, "[common]", "temp_measurement_interval", tempInterval.value);
content = setValueByKey(content, "[common]", "temp_overheat_warning", tempWarning.value);
// Drone MAVLink Configuration
const droneMavlinkPeer = `${droneMavlinkType.value}://${droneMavlinkIP.value}:${droneMavlinkPort.value}`;
content = setValueByKey(content, "[drone_mavlink]", "fwmark", droneMavlinkFwmark.value);
content = setValueByKey(content, "[drone_mavlink]", "peer", droneMavlinkPeer);
// Drone Video Configuration
const droneVideoPeer = `${droneVideoType.value}://${droneVideoIP.value}:${droneVideoPort.value}`;
content = setValueByKey(content, "[drone_video]", "fwmark", droneVideoFwmark.value);
content = setValueByKey(content, "[drone_video]", "peer", droneVideoPeer);
// MAVLink Configuration
content = setValueByKey(content, "[mavlink]", "inject_rssi", injectRssi.checked ? "True" : "False");
content = setValueByKey(content, "[mavlink]", "mavlink_sys_id", mavlinkSysId.value);
content = setValueByKey(content, "[mavlink]", "mavlink_comp_id", mavlinkCompId.value);
content = setValueByKey(content, "[mavlink]", "mavlink_tcp_port", mavlinkTcpPort.value === "" ? "None" : mavlinkTcpPort.value);
cockpit.file(confLocation, { superuser: "try" }).replace(content)
.then(() => {
console.log("Configuration saved successfully.");
RestartWifibroadcastService();
Success();
})
.catch((error) => {
console.error("Failed to save configuration:", error);
Fail(error);
});
})
.catch(error => {
console.error("Failed to read configuration file:", error);
FailureReadFile(error);
});
} catch (e) {
console.error("Error during save operation:", e);
FailureReadFile(e);
}
}
// Apply encryption file from base station
function ApplyEncryptionFile() {
const command = `
/usr/bin/sshpass -p 'spirifriend' scp -o StrictHostKeyChecking=no spiri@spiri-base.local:/home/spiri/drone.key /tmp/drone.key &&
/usr/bin/sudo /bin/mv /tmp/drone.key ${encryptionFileLocation} &&
/usr/bin/sudo /bin/systemctl restart wifibroadcast@drone
`;
cockpit.spawn(["bash", "-c", command], { superuser: "require" })
.then(() => {
console.log("Encryption key applied and wifibroadcast service restarted.");
Success();
})
.catch((error) => {
console.error("Failed to apply encryption key or restart service:", error);
Fail(error);
});
}
// Restart wifibroadcast service
function RestartWifibroadcastService() {
cockpit.spawn(["systemctl", "restart", "wifibroadcast@drone"], { superuser: "require" })
.then(() => {
console.log("wifibroadcast@drone service restarted.");
})
.catch((error) => {
console.error("Failed to restart wifibroadcast@drone service:", error);
});
}
// Success handler
function Success() {
result.style.color = "green";
result.innerHTML = "Success, settings saved.";
setTimeout(() => result.innerHTML = "", 5000);
}
// Failure handler
function Fail(error) {
result.style.color = "red";
result.innerHTML = error.message;
}
// Parse Peer configuration (type, IP, port)
function parsePeer(peer, typeElement, ipElement, portElement) {
if (!peer) {
console.warn("Peer value is empty or undefined.");
return; // Skip if peer is empty
}
const regex = /(listen|connect):\/\/([\d.]+):(\d+)/;
const match = peer.match(regex);
if (match) {
typeElement.value = match[1];
ipElement.value = match[2];
portElement.value = match[3];
} else {
console.error("Failed to parse peer:", peer);
}
}
// Get value by key from configuration content
function getValueByKey(content, section, key) {
const sectionRegex = new RegExp(`\\[${section}\\][\\s\\S]*?^${key}\\s*=\\s*(.*)`, "m");
const match = content.match(sectionRegex);
if (match) {
let value = match[1].trim();
console.log(`Found key [${key}] in section [${section}]:`, value);
return value;
} else {
console.warn(`Key [${key}] not found in section [${section}]`);
return "";
}
}
// Set value by key in configuration content
function setValueByKey(content, section, key, value) {
const regex = new RegExp(`(${section}[\\s\\S]*?)(${key}\\s*=\\s*)(.*)`, "m");
if (content.match(regex)) {
return content.replace(regex, `$1$2${value}`);
} else {
// Add key if not found
const sectionRegex = new RegExp(`(${section}[\\s\\S]*?)\\n`, "m");
return content.match(sectionRegex)
? content.replace(sectionRegex, `$1\n${key} = ${value}\n`)
: `${content}\n${section}\n${key} = ${value}\n`;
}
}

View File

@ -1,12 +1,20 @@
[common]
wifi_channel = 161 # 161 -- radio channel @5825 MHz, range: 58155835 MHz, width 20MHz
# 1 -- radio channel @2412 Mhz,
# see https://en.wikipedia.org/wiki/List_of_WLAN_channels for reference
wifi_region = 'BO' # Your country for CRDA (use BO or GY if you want max tx power)
wifi_channel = 161
wifi_region = 'BO'
temp_measurement_interval = 10
temp_overheat_warning = 60
wifi_txpower = None
[drone_mavlink]
peer = 'listen://0.0.0.0:14550' # incoming connection
#peer = 'connect://127.0.0.1:14550' # outgoing connection
fwmark = 10
peer = 'listen://0.0.0.0:14550'
[drone_video]
peer = 'listen://0.0.0.0:5602' # listen for video stream (gstreamer on drone)
fwmark = 20
peer = 'listen://0.0.0.0:5602'
[mavlink]
inject_rssi = True
mavlink_sys_id = 3
mavlink_comp_id = 68
mavlink_tcp_port = None