mirror of
https://github.com/Genaker/LoraSA.git
synced 2026-03-28 17:42:59 +01:00
compass APP
This commit is contained in:
@@ -453,6 +453,7 @@ void SetDioAsRfSwitch()
|
|||||||
void initRadio(float freq)
|
void initRadio(float freq)
|
||||||
{
|
{
|
||||||
int state, state2;
|
int state, state2;
|
||||||
|
radio.begin();
|
||||||
state = radio.beginGFSK(freq, 4.8F, 5.0F, 156.2F, 10, 16U, 1.6F);
|
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);
|
state2 = radio2.beginGFSK(freq, 4.8F, 5.0F, 156.2F, 10, 16U, 1.6F);
|
||||||
if (state != RADIOLIB_ERR_NONE)
|
if (state != RADIOLIB_ERR_NONE)
|
||||||
|
|||||||
@@ -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
|
// Search for parameter in HTTP POST request
|
||||||
const String SSID = "ssid";
|
const String SSID = "ssid";
|
||||||
const String PASS = "pass";
|
const String PASS = "pass";
|
||||||
@@ -21,6 +13,15 @@ const String FEND = "fend";
|
|||||||
String ssid = "LoraSA", pass = "1234567890", ip = "192.168.1.100",
|
String ssid = "LoraSA", pass = "1234567890", ip = "192.168.1.100",
|
||||||
gateway = "192.168.1.1", fstart = "", fend = "", smpls = "";
|
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;
|
IPAddress localIP;
|
||||||
// Set your Gateway IP address
|
// Set your Gateway IP address
|
||||||
IPAddress localGateway;
|
IPAddress localGateway;
|
||||||
@@ -73,25 +74,6 @@ bool initWiFi()
|
|||||||
return true;
|
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()
|
void serverServer()
|
||||||
{
|
{
|
||||||
// Route for root / web page
|
// Route for root / web page
|
||||||
@@ -203,3 +185,23 @@ void serverStart()
|
|||||||
serverServer();
|
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());
|
||||||
|
}
|
||||||
|
|||||||
@@ -263,9 +263,10 @@ lib_deps =
|
|||||||
RadioLib
|
RadioLib
|
||||||
U8g2
|
U8g2
|
||||||
XPowersLib
|
XPowersLib
|
||||||
ESP Async WebServer
|
h2zero/NimBLE-Arduino
|
||||||
https://github.com/Genaker/Adafruit_HMC5883_Unified
|
https://github.com/Genaker/Adafruit_HMC5883_Unified
|
||||||
build_flags =
|
build_flags =
|
||||||
|
-lbt
|
||||||
-DLILYGO
|
-DLILYGO
|
||||||
-DT3_S3_V1_2_LR1121
|
-DT3_S3_V1_2_LR1121
|
||||||
-DARDUINO_LILYGO_T3S3_LR1121
|
-DARDUINO_LILYGO_T3S3_LR1121
|
||||||
@@ -285,6 +286,7 @@ build_flags =
|
|||||||
-DCOMPASS_FREQ=915
|
-DCOMPASS_FREQ=915
|
||||||
-DCOMPASS_DEBUG
|
-DCOMPASS_DEBUG
|
||||||
-DCOMPASS_RSSI
|
-DCOMPASS_RSSI
|
||||||
|
-DBT_MOBILE
|
||||||
|
|
||||||
[env:lilygo-T3S3-v1-2-sx1280]
|
[env:lilygo-T3S3-v1-2-sx1280]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
|
|||||||
70
src/main.cpp
70
src/main.cpp
@@ -23,10 +23,70 @@
|
|||||||
|
|
||||||
// #define HELTEC_NO_DISPLAY
|
// #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 "FS.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#ifdef WEB_SERVER
|
||||||
#include <AsyncTCP.h>
|
#include <AsyncTCP.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#endif
|
||||||
#include <File.h>
|
#include <File.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
@@ -1416,6 +1476,10 @@ void setup(void)
|
|||||||
readConfigFile();
|
readConfigFile();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef BT_MOBILE
|
||||||
|
initBT();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef WEB_SERVER
|
#ifndef WEB_SERVER
|
||||||
if (config.scan_ranges_sz == 0 && SCAN_RANGES.length() > 0)
|
if (config.scan_ranges_sz == 0 && SCAN_RANGES.length() > 0)
|
||||||
{
|
{
|
||||||
@@ -2414,6 +2478,9 @@ void loop(void)
|
|||||||
// DEBUG STUFF
|
// DEBUG STUFF
|
||||||
/*String((int)headingDegrees) + "x:" + String(newX) +
|
/*String((int)headingDegrees) + "x:" + String(newX) +
|
||||||
"c:" + String(xResolution) + "l:" + String(length)*/);
|
"c:" + String(xResolution) + "l:" + String(length)*/);
|
||||||
|
#ifdef BT_MOBILE
|
||||||
|
sendBTData(headingDegrees, rssiMax); // Send data to BLE client
|
||||||
|
#endif
|
||||||
|
|
||||||
display.display();
|
display.display();
|
||||||
compassCounter++;
|
compassCounter++;
|
||||||
@@ -2429,6 +2496,9 @@ void loop(void)
|
|||||||
{
|
{
|
||||||
historicalCompassRssi[i] = 999;
|
historicalCompassRssi[i] = 999;
|
||||||
}
|
}
|
||||||
|
initForScan(FREQ_BEGIN);
|
||||||
|
// Restart BT advert
|
||||||
|
pAdvertising->start();
|
||||||
maxRssiHist = 9999;
|
maxRssiHist = 9999;
|
||||||
maxRssiHistX = 130;
|
maxRssiHistX = 130;
|
||||||
maxRssiHeading = 0;
|
maxRssiHeading = 0;
|
||||||
|
|||||||
438
web_app/compass/index.html
Normal file
438
web_app/compass/index.html
Normal 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">0°</span></p>
|
||||||
|
<p>RSSI: <span id="rssi">-70 dBm</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<p>Heading MAX: <span id="heading-max">0°</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
|
||||||
Reference in New Issue
Block a user