Compare commits
No commits in common. "67e1a78995842c63c87816e94088acc9dbb4f1e3" and "38b80f97e56331e51786469593c00c76f3784a1b" have entirely different histories.
67e1a78995
...
38b80f97e5
3 changed files with 245 additions and 711 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,157 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Fieldtest Positions</title>
|
|
||||||
|
|
||||||
<!-- Leaflet (swap to your local staged copies if preferred)
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> -->
|
|
||||||
<link rel="stylesheet" href="https://ryzdesk/lib/leaflet.css">
|
|
||||||
<script src="https://ryzdesk/lib/leaflet.js"></script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
html, body { height: 100%; margin: 0; }
|
|
||||||
#map { height: 100%; width: 100%; }
|
|
||||||
.legend {
|
|
||||||
position: absolute; z-index: 1000; background: rgba(255,255,255,0.85);
|
|
||||||
padding: 8px; margin: 10px; border-radius: 6px; font-family: sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.node-label {
|
|
||||||
background: rgba(255,255,255,0.85);
|
|
||||||
border: 1px solid rgba(0,0,0,0.25);
|
|
||||||
color: #000;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 1px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="legend">
|
|
||||||
WS: <span id="ws_state">connecting</span><br>
|
|
||||||
Last: <span id="last_msg">—</span>
|
|
||||||
</div>
|
|
||||||
<div id="map"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// --- Map init (center near your sample points; adjust as desired)
|
|
||||||
const map = L.map('map').setView([44.93655, -123.02185], 17);
|
|
||||||
|
|
||||||
// Basemap: replace with your own tile server if you have one
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
maxZoom: 20,
|
|
||||||
attribution: '© OpenStreetMap contributors'
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
// Node colors (bright, readable)
|
|
||||||
const nodeStyle = {
|
|
||||||
B: { color: '#00e5ff' }, // cyan-ish
|
|
||||||
C: { color: '#ffea00' }, // yellow
|
|
||||||
D: { color: '#ff3d00' }, // orange/red
|
|
||||||
E: { color: '#76ff03' }, // green
|
|
||||||
};
|
|
||||||
// One persistent "current" marker per node (bright + labeled)
|
|
||||||
const currentMarkers = {}; // node -> L.CircleMarker
|
|
||||||
|
|
||||||
function addFadingDot(node, lat, lon) {
|
|
||||||
const style = nodeStyle[node] || { color: '#ffffff' };
|
|
||||||
|
|
||||||
// Bright at birth
|
|
||||||
const m = L.circleMarker([lat, lon], {
|
|
||||||
radius: 7,
|
|
||||||
color: style.color,
|
|
||||||
weight: 2,
|
|
||||||
fillColor: style.color,
|
|
||||||
fillOpacity: 1.0,
|
|
||||||
opacity: 1.0
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
// Fade steps: 5s -> 2/3, 10s -> 1/3, 15s -> remove
|
|
||||||
setTimeout(() => {
|
|
||||||
m.setStyle({ fillOpacity: 0.66, opacity: 0.66 });
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
m.setStyle({ fillOpacity: 0.33, opacity: 0.33 });
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
map.removeLayer(m);
|
|
||||||
}, 15000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function upsertCurrentMarker(node, lat, lon) {
|
|
||||||
const style = nodeStyle[node] || { color: '#ffffff' };
|
|
||||||
|
|
||||||
if (!currentMarkers[node]) {
|
|
||||||
const m = L.circleMarker([lat, lon], {
|
|
||||||
radius: 9,
|
|
||||||
color: style.color,
|
|
||||||
weight: 2,
|
|
||||||
fillColor: style.color,
|
|
||||||
fillOpacity: 1.0,
|
|
||||||
opacity: 1.0
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
// Permanent label next to the current marker
|
|
||||||
m.bindTooltip(node, {
|
|
||||||
permanent: true,
|
|
||||||
direction: 'right',
|
|
||||||
offset: [10, 0],
|
|
||||||
className: 'node-label'
|
|
||||||
});
|
|
||||||
|
|
||||||
currentMarkers[node] = m;
|
|
||||||
} else {
|
|
||||||
currentMarkers[node].setLatLng([lat, lon]);
|
|
||||||
// reassert brightness in case you later add effects
|
|
||||||
currentMarkers[node].setStyle({ fillOpacity: 1.0, opacity: 1.0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function parseMsg(line) {
|
|
||||||
// "C,44.936454,-123.021923,65.40"
|
|
||||||
const parts = line.trim().split(',');
|
|
||||||
if (parts.length < 4) return null;
|
|
||||||
const node = parts[0];
|
|
||||||
const lat = parseFloat(parts[1]);
|
|
||||||
const lon = parseFloat(parts[2]);
|
|
||||||
const alt = parseFloat(parts[3]); // not used yet, but keep it
|
|
||||||
if (!node || Number.isNaN(lat) || Number.isNaN(lon)) return null;
|
|
||||||
return { node, lat, lon, alt };
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- WebSocket
|
|
||||||
const wsStateEl = document.getElementById('ws_state');
|
|
||||||
const lastMsgEl = document.getElementById('last_msg');
|
|
||||||
|
|
||||||
//const wsUrl = `ws://${location.hostname}:3000/stream`;
|
|
||||||
//const ws = new WebSocket(wsUrl);
|
|
||||||
|
|
||||||
const wsScheme = (location.protocol === 'https:') ? 'wss' : 'ws';
|
|
||||||
const wsUrl = (wsScheme === 'ws')
|
|
||||||
? `ws://${location.hostname}:3000/stream`
|
|
||||||
: `wss://${location.host}/stream`;
|
|
||||||
|
|
||||||
const ws = new WebSocket(wsUrl);
|
|
||||||
//const ws = new WebSocket('ws://localhost:3000/stream'); // temporary test
|
|
||||||
|
|
||||||
ws.onopen = () => wsStateEl.textContent = 'open';
|
|
||||||
ws.onclose = () => wsStateEl.textContent = 'closed';
|
|
||||||
ws.onerror = () => wsStateEl.textContent = 'error';
|
|
||||||
|
|
||||||
ws.onmessage = (ev) => {
|
|
||||||
const line = ev.data;
|
|
||||||
lastMsgEl.textContent = line;
|
|
||||||
|
|
||||||
const msg = parseMsg(line);
|
|
||||||
if (!msg) return;
|
|
||||||
|
|
||||||
addFadingDot(msg.node, msg.lat, msg.lon);
|
|
||||||
upsertCurrentMarker(msg.node, msg.lat, msg.lon);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
#!/usr/bin/env perl
|
|
||||||
# 20260219 ChatGPT
|
|
||||||
# $Id$
|
|
||||||
# $HeadURL$
|
|
||||||
|
|
||||||
# EXAMPLE:
|
|
||||||
# perl fwd_positions_udp.pl \
|
|
||||||
# --file /usr/local/src/sx1302_hal/util_net_downlink/uplinks_20260219_123102.csv \
|
|
||||||
# --host ryzdesk --port 1777
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use IO::Socket::INET;
|
|
||||||
use Getopt::Long qw(GetOptions);
|
|
||||||
|
|
||||||
my ($file, $host, $port) = ("", "ryzdesk", 1777);
|
|
||||||
GetOptions(
|
|
||||||
"file=s" => \$file,
|
|
||||||
"host=s" => \$host,
|
|
||||||
"port=i" => \$port,
|
|
||||||
) or die "bad args\n";
|
|
||||||
|
|
||||||
die "--file required\n" if !$file;
|
|
||||||
|
|
||||||
my $sock = IO::Socket::INET->new(
|
|
||||||
PeerAddr => $host,
|
|
||||||
PeerPort => $port,
|
|
||||||
Proto => "udp",
|
|
||||||
) or die "udp socket: $!\n";
|
|
||||||
|
|
||||||
£ Use tail -F so rotations keep working
|
|
||||||
open(my $fh, "-|", "tail", "-F", $file) or die "tail: $!\n";
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
|
|
||||||
while (my $line = <$fh>) {
|
|
||||||
chomp $line;
|
|
||||||
|
|
||||||
£ Your CSV has the hex payload in field 16 (1-based) => index 15 (0-based)
|
|
||||||
my @f = split(/,/, $line, -1);
|
|
||||||
next if @f < 16;
|
|
||||||
|
|
||||||
my $hex = $f[15] // "";
|
|
||||||
$hex =~ s/[^0-9A-Fa-f]//g;
|
|
||||||
next if $hex eq "";
|
|
||||||
|
|
||||||
my $msg = pack("H*", $hex); £ yields: "C,44.936454,-123.021923,65.40"
|
|
||||||
$msg =~ s/\r?\n$//;
|
|
||||||
|
|
||||||
£ sanity filter: must look like: X,lat,lon,alt
|
|
||||||
next unless $msg =~ /^[A-Z],[+-]?\d+\.\d+,[+-]?\d+\.\d+,[+-]?\d+(\.\d+)?$/;
|
|
||||||
|
|
||||||
$sock->send($msg."\n");
|
|
||||||
print "$msg\n"; £ local visibility
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue