compass APP

This commit is contained in:
Egor
2025-01-27 20:42:03 -08:00
parent 18b95c75a8
commit 77f7daa333
5 changed files with 542 additions and 29 deletions

View File

@@ -453,6 +453,7 @@ void SetDioAsRfSwitch()
void initRadio(float freq)
{
int state, state2;
radio.begin();
state = radio.beginGFSK(freq, 4.8F, 5.0F, 156.2F, 10, 16U, 1.6F);
state2 = radio2.beginGFSK(freq, 4.8F, 5.0F, 156.2F, 10, 16U, 1.6F);
if (state != RADIOLIB_ERR_NONE)

View File

@@ -1,11 +1,3 @@
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <WiFi.h>
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// Search for parameter in HTTP POST request
const String SSID = "ssid";
const String PASS = "pass";
@@ -21,6 +13,15 @@ const String FEND = "fend";
String ssid = "LoraSA", pass = "1234567890", ip = "192.168.1.100",
gateway = "192.168.1.1", fstart = "", fend = "", smpls = "";
#ifdef WEB_SERVER
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <WiFi.h>
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
IPAddress localIP;
// Set your Gateway IP address
IPAddress localGateway;
@@ -73,25 +74,6 @@ bool initWiFi()
return true;
}
void writeParameterToFile(String value, String file)
{
// Write file to save value
writeFile(LittleFS, file.c_str(), value.c_str());
}
void writeParameterToParameterFile(String param, String value)
{
String file = String("/" + param + ".txt");
// Write file to save value
writeParameterToFile(value, file.c_str());
}
String readParameterFromParameterFile(String param)
{
String file = String("/" + param + ".txt");
return readFile(LittleFS, file.c_str());
}
void serverServer()
{
// Route for root / web page
@@ -203,3 +185,23 @@ void serverStart()
serverServer();
}
}
#endif
void writeParameterToFile(String value, String file)
{
// Write file to save value
writeFile(LittleFS, file.c_str(), value.c_str());
}
void writeParameterToParameterFile(String param, String value)
{
String file = String("/" + param + ".txt");
// Write file to save value
writeParameterToFile(value, file.c_str());
}
String readParameterFromParameterFile(String param)
{
String file = String("/" + param + ".txt");
return readFile(LittleFS, file.c_str());
}

View File

@@ -263,9 +263,10 @@ lib_deps =
RadioLib
U8g2
XPowersLib
ESP Async WebServer
h2zero/NimBLE-Arduino
https://github.com/Genaker/Adafruit_HMC5883_Unified
build_flags =
-lbt
-DLILYGO
-DT3_S3_V1_2_LR1121
-DARDUINO_LILYGO_T3S3_LR1121
@@ -285,6 +286,7 @@ build_flags =
-DCOMPASS_FREQ=915
-DCOMPASS_DEBUG
-DCOMPASS_RSSI
-DBT_MOBILE
[env:lilygo-T3S3-v1-2-sx1280]
platform = espressif32

View File

@@ -23,10 +23,70 @@
// #define HELTEC_NO_DISPLAY
#ifdef BT_MOBILE
#include <NimBLEDevice.h>
#define SERVICE_UUID "00001234-0000-1000-8000-00805f9b34fb"
#define CHARACTERISTIC_UUID "00001234-0000-1000-8000-00805f9b34ac"
NimBLEServer *pServer = nullptr;
NimBLECharacteristic *pCharacteristic = nullptr;
NimBLEAdvertising *pAdvertising = nullptr;
void initBT()
{
// Initialize BLE device
NimBLEDevice::init("RSSI_Radar");
// Get and print the MAC address
String macAddress = NimBLEDevice::getAddress().toString().c_str();
Serial.println("Bluetooth MAC Address: " + macAddress);
// Create BLE server
pServer = NimBLEDevice::createServer();
// Create a BLE service
NimBLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
// Start the service
pService->start();
// Start advertising
pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setName("ESP32_RSSI_Radar"); // Set the device name
pAdvertising->setMinInterval(300);
pAdvertising->setMaxInterval(350);
// pAdvertising->setScanResponse(true); // Allow scan responses
pAdvertising->start();
Serial.println("BLE server started.");
}
// Function to send RSSI and Heading Data
void sendBTData(float heading, float rssi)
{
String data =
"RSSI_HEADING: '{H:" + String(heading) + ",RSSI:-" + String(rssi) + "}'";
Serial.println("Sending data: " + data);
pCharacteristic->setValue(data.c_str()); // Set BLE characteristic value
pCharacteristic->notify(); // Notify connected client
}
#endif
#include "FS.h"
#include <Arduino.h>
#ifdef WEB_SERVER
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#endif
#include <File.h>
#include <LittleFS.h>
#include <Wire.h>
@@ -1416,6 +1476,10 @@ void setup(void)
readConfigFile();
#endif
#ifdef BT_MOBILE
initBT();
#endif
#ifndef WEB_SERVER
if (config.scan_ranges_sz == 0 && SCAN_RANGES.length() > 0)
{
@@ -2414,6 +2478,9 @@ void loop(void)
// DEBUG STUFF
/*String((int)headingDegrees) + "x:" + String(newX) +
"c:" + String(xResolution) + "l:" + String(length)*/);
#ifdef BT_MOBILE
sendBTData(headingDegrees, rssiMax); // Send data to BLE client
#endif
display.display();
compassCounter++;
@@ -2429,6 +2496,9 @@ void loop(void)
{
historicalCompassRssi[i] = 999;
}
initForScan(FREQ_BEGIN);
// Restart BT advert
pAdvertising->start();
maxRssiHist = 9999;
maxRssiHistX = 130;
maxRssiHeading = 0;

438
web_app/compass/index.html Normal file
View File

@@ -0,0 +1,438 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSSI Radar</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin: 0;
padding: 20px;
background-color: #0a0a0a;
color: white;
}
#radarCanvas {
margin: 20px auto;
display: block;
background-color: #001f00;
border-radius: 50%;
box-shadow: 0 0 20px rgba(0, 255, 0, 0.5);
}
#legend {
margin-top: 20px;
padding: 10px;
background-color: rgba(0, 255, 0, 0.1);
border-radius: 5px;
width: 300px;
margin-left: auto;
margin-right: auto;
border: 1px solid rgba(0, 255, 0, 0.5);
}
button {
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 5px;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s ease;
margin: 5px;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #a9a9a9;
cursor: not-allowed;
}
.info {
margin-top: 20px;
font-size: 18px;
}
#deviceList {
margin-top: 20px;
padding: 10px;
background-color: rgba(0, 255, 0, 0.1);
border-radius: 5px;
width: 300px;
margin-left: auto;
margin-right: auto;
border: 1px solid rgba(0, 255, 0, 0.5);
}
#deviceList ul {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
#deviceList li {
padding: 5px;
cursor: pointer;
}
#deviceList li:hover {
background-color: rgba(0, 255, 0, 0.2);
}
</style>
</head>
<body>
<h1>RSSI Radar</h1>
<canvas id="radarCanvas" width="500" height="500"></canvas>
<!--
<div id="legend">
<p><strong>Legend:</strong></p>
<ul style="list-style: none; padding: 0; margin: 0; text-align: left;">
<li><span style="color: red;">Red</span>: RSSI Heading Vectors</li>
<li><span style="color: #e0ffe0;">Light Green</span>: Compass Directions (45° steps)</li>
</ul>
</div>-->
<div class="info">
<p>Heading: <span id="heading"></span></p>
<p>RSSI: <span id="rssi">-70 dBm</span></p>
</div>
<div class="info">
<p>Heading MAX: <span id="heading-max"></span></p>
<p>RSSI MAX: <span id="rssi-max">-70 dBm</span></p>
</div>
<p>Status: <span id="status">Disconnected</span></p>
<button id="scanBtn">Scan for Bluetooth Devices</button>
<button id="simulateBtn">Simulate Random Data</button>
<div id="deviceList" style="display: none;">
<p><strong>Available Devices:</strong></p>
<ul id="devices"></ul>
</div>
<script>
const canvas = document.getElementById("radarCanvas");
const ctx = canvas.getContext("2d");
const headingDisplay = document.getElementById("heading");
const rssiDisplay = document.getElementById("rssi");
const headingDisplayMAX = document.getElementById("heading-max");
const rssiDisplayMAX = document.getElementById("rssi-max");
const scanBtn = document.getElementById("scanBtn");
const simulateBtn = document.getElementById("simulateBtn");
const deviceList = document.getElementById("deviceList");
const devicesUl = document.getElementById("devices");
const statusElem = document.getElementById("status");
let isSimulating = false; // Flag to track simulation state
let dataPoints = [{ angle: 0, rssi: -120 }]; // Array to store all received data
let currentPoint = { angle: 0, rssi: -120 }; // To track the most recently added point
// Draw radar function
function drawRadar() {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 10;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Radar background
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.strokeStyle = "#00ff00";
ctx.lineWidth = 2;
ctx.stroke();
// Concentric circles
for (let i = 1; i <= 3; i++) {
ctx.beginPath();
ctx.arc(centerX, centerY, radius * (i / 3), 0, 2 * Math.PI);
ctx.strokeStyle = "rgba(0, 255, 0, 0.3)";
ctx.lineWidth = 1;
ctx.stroke();
}
// Compass lines
for (let angle = 0; angle < 360; angle += 45) {
const rad = (angle * Math.PI) / 180;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(centerX + radius * Math.cos(rad), centerY + radius * Math.sin(rad));
ctx.strokeStyle = "#e0ffe0";
ctx.lineWidth = 1;
ctx.stroke();
}
// Draw data points
dataPoints.forEach(({ angle, rssi }) => {
const rad = (angle * Math.PI) / 180;
const length = ((120 + rssi) / (radius / 2)) * radius; // Scale RSSI to fit within radar
const x = centerX + length * Math.cos(rad);
const y = centerY + length * Math.sin(rad);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(x, y);
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.stroke();
});
const maxPoint = dataPoints.reduce((max, point) => (point.rssi > max.rssi ? point : max), { angle: 0, rssi: -120 });
const maxRad = (maxPoint.angle * Math.PI) / 180;
const maxLength = radius;
headingDisplayMAX.textContent = `${maxPoint.angle.toFixed(1)}°`;
rssiDisplayMAX.textContent = `${maxPoint.rssi.toFixed(1)} dBm`;
const maxX = centerX + maxLength * Math.cos(maxRad);
const maxY = centerY + maxLength * Math.sin(maxRad);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(maxX, maxY);
ctx.strokeStyle = "blue";
ctx.lineWidth = 3;
ctx.stroke();
// Draw arrowhead
const arrowHeadLength = 10;
const arrowAngle = Math.PI / 6; // 30 degrees for the arrowhead
const arrowX1 = maxX - arrowHeadLength * Math.cos(maxRad - arrowAngle);
const arrowY1 = maxY - arrowHeadLength * Math.sin(maxRad - arrowAngle);
const arrowX2 = maxX - arrowHeadLength * Math.cos(maxRad + arrowAngle);
const arrowY2 = maxY - arrowHeadLength * Math.sin(maxRad + arrowAngle);
ctx.beginPath();
ctx.moveTo(maxX, maxY);
ctx.lineTo(arrowX1, arrowY1);
ctx.lineTo(arrowX2, arrowY2);
ctx.closePath();
ctx.fillStyle = "blue";
ctx.fill();
ctx.stroke();
// Draw current RSSI line in yellow
const currentRad = (currentPoint.angle * Math.PI) / 180;
const currentLength = ((120 + currentPoint.rssi) / (radius / 2)) * radius;
const currentX = centerX + currentLength * Math.cos(currentRad);
const currentY = centerY + currentLength * Math.sin(currentRad);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(currentX, currentY);
ctx.strokeStyle = "yellow";
ctx.lineWidth = 3;
ctx.stroke();
}
// Handle canvas hover for pointer change
canvas.addEventListener("mousemove", (event) => {
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
let hovering = false;
dataPoints.forEach(({ angle, rssi }) => {
const rad = (angle * Math.PI) / 180;
const length = ((120 + rssi) / (centerX / 2)) * centerX; // Scale RSSI to fit within radar
const x = centerX + length * Math.cos(rad);
const y = centerY + length * Math.sin(rad);
const distance = Math.sqrt(Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2));
if (distance < 10) {
hovering = true;
}
});
canvas.style.cursor = hovering ? "pointer" : "default";
});
// Handle canvas clicks
canvas.addEventListener("click", (event) => {
const rect = canvas.getBoundingClientRect();
const clickX = event.clientX - rect.left;
const clickY = event.clientY - rect.top;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
dataPoints.forEach(({ angle, rssi }) => {
const rad = (angle * Math.PI) / 180;
const length = ((120 + rssi) / (centerX / 2)) * centerX; // Scale RSSI to fit within radar
const x = centerX + length * Math.cos(rad);
const y = centerY + length * Math.sin(rad);
const distance = Math.sqrt(Math.pow(clickX - x, 2) + Math.pow(clickY - y, 2));
if (distance < 10) { // If the click is close to the endpoint of the line
showPrompt(`Angle: ${angle}°`, `RSSI: ${rssi} dBm`);
}
});
});
// Show a custom prompt
function showPrompt(title, message) {
const promptDiv = document.createElement("div");
promptDiv.style.position = "fixed";
promptDiv.style.top = "50%";
promptDiv.style.left = "50%";
promptDiv.style.transform = "translate(-50%, -50%)";
promptDiv.style.backgroundColor = "#333";
promptDiv.style.color = "white";
promptDiv.style.padding = "20px";
promptDiv.style.border = "2px solid #00ff00";
promptDiv.style.borderRadius = "10px";
promptDiv.style.zIndex = "1000";
const titleElem = document.createElement("h2");
titleElem.textContent = title;
titleElem.style.margin = "0 0 10px 0";
const messageElem = document.createElement("p");
messageElem.textContent = message;
messageElem.style.margin = "0 0 20px 0";
const closeButton = document.createElement("button");
closeButton.textContent = "Close";
closeButton.style.padding = "10px 20px";
closeButton.style.backgroundColor = "#007bff";
closeButton.style.color = "white";
closeButton.style.border = "none";
closeButton.style.borderRadius = "5px";
closeButton.style.cursor = "pointer";
closeButton.addEventListener("click", () => {
document.body.removeChild(promptDiv);
});
promptDiv.appendChild(titleElem);
promptDiv.appendChild(messageElem);
promptDiv.appendChild(closeButton);
document.body.appendChild(promptDiv);
}
// Parse Bluetooth data
function parseBTData(data) {
// Match the data format and extract the heading and RSSI values
const match = data.match(/RSSI_HEADING: '\{H:(\d+\.\d+),RSSI:(-?\d+\.\d+|-?\d+)\}'/);
if (match) {
const heading = parseInt(match[1]);
const rssi = parseFloat(match[2]);
console.log("H:" + heading + " R:" + rssi);
dataPoints[parseInt(heading)] = { angle: parseInt(heading), rssi: rssi };
currentPoint = { angle: parseInt(heading), rssi: rssi };
//if (dataPoints.length > 50) dataPoints.shift(); // Keep only the last 50 points
headingDisplay.textContent = `${heading.toFixed(1)}°`;
rssiDisplay.textContent = `${rssi.toFixed(1)} dBm`;
drawRadar();
}
}
// Scan for Bluetooth devices
scanBtn.addEventListener("click", async () => {
try {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: ['00001234-0000-1000-8000-00805f9b34fb']
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService('00001234-0000-1000-8000-00805f9b34fb');
const characteristic = await service.getCharacteristic('00001234-0000-1000-8000-00805f9b34ac');
// Save the device's ID to localStorage
localStorage.setItem("bluetoothDeviceId", device.id);
characteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = new TextDecoder().decode(event.target.value); // Decode the data
console.log("Received data:", value); // For debugging
parseBTData(value); // Process the data with your existing function
});
await characteristic.startNotifications();
//alert(`Connected to ${device.name}`);
statusElem.textContent = "Connected";
} catch (error) {
console.error("Error scanning for devices:", error);
alert("Could not connect to any device.");
statusElem.textContent = "Connection Error";
}
});
// Simulate random data
simulateBtn.addEventListener("click", () => {
if (isSimulating) {
isSimulating = false;
simulateBtn.textContent = "Simulate Random Data";
} else {
isSimulating = true;
simulateBtn.textContent = "Stop Simulation";
(function simulate() {
if (!isSimulating) return;
const angle = Math.random() * 360;
const rssi = -70 + Math.random() * 30;
dataPoints.push({ angle, rssi });
currentPoint = { angle, rssi };
if (dataPoints.length > 360 * 5) dataPoints.shift();
headingDisplay.textContent = `${angle.toFixed(1)}°`;
rssiDisplay.textContent = `${rssi.toFixed(1)} dBm`;
drawRadar();
setTimeout(simulate, 100);
})();
}
});
// Function to reconnect to a previously paired device
async function reconnectBluetooth() {
try {
// Get the saved device ID from localStorage
const savedDeviceId = localStorage.getItem("bluetoothDeviceId");
if (!savedDeviceId) {
console.log("No saved device found.");
statusElem.textContent = "No saved device found.";
return;
}
// Find the saved device in the browser's cache
const devices = await navigator.bluetooth.getDevices();
bluetoothDevice = devices.find((device) => device.id === savedDeviceId);
if (!bluetoothDevice) {
console.log("Saved device not available.");
statusElem.textContent = "Saved device not available.";
return;
}
console.log(`Reconnecting to device: ${bluetoothDevice.name}`);
const server = await bluetoothDevice.gatt.connect();
console.log(`Reconnected to device: ${bluetoothDevice.name}`);
deviceNameElem.textContent = bluetoothDevice.name;
statusElem.textContent = "Reconnected";
} catch (error) {
console.error("Error reconnecting to Bluetooth device:", error);
statusElem.textContent = "Reconnection Failed";
}
}
// Automatically reconnect on page load
window.addEventListener("load", reconnectBluetooth);
// Initial draw
drawRadar();
</script>
</body>
</html