mirror of
https://github.com/colonelpanichacks/flock-you.git
synced 2026-07-30 10:18:09 -07:00
dashboard: add gpsd as a GPS source
The GPS control was serial-only. Adds a source picker (Serial NMEA /
gpsd) to the dashboard header:
- Backend: new gpsd_reader() thread speaks gpsd's line-delimited JSON
protocol directly (no gps client dep). Parses TPV/SKY into the same
gps_data + gps_history structure as the NMEA reader, so temporal
matching, exports, and everything downstream stay source-agnostic.
- /api/gps/connect now dispatches on source=serial|gpsd. Legacy body
{"port": "..."} still works and is treated as serial.
- /api/status reports gps_source and, for gpsd, gpsd_endpoint.
- Reconnect helper remembers which source was live and retries the
same endpoint.
- Frontend: source dropdown toggles between the serial port list and
a gpsd host:port input pair. Settings persist gps_source /
gpsd_host / gpsd_port alongside gps_port.
Useful when a system-wide GPS is already shared via gpsd (laptop puck
with gpsd -N, Android Bluetooth GPS bridged through gpsd, Pi feeding
several tools). Browser Geolocation source is unchanged.
This commit is contained in:
@@ -166,10 +166,11 @@ The firmware emits one JSON line per detection in the same schema the BLE detect
|
||||
|
||||
### GPS wardriving
|
||||
|
||||
GPS is handled Flask-side, since the ESP32 radio is dedicated to sniffing and there's no on-device AP. Two options:
|
||||
GPS is handled Flask-side, since the ESP32 radio is dedicated to sniffing and there's no on-device AP. Three options, all selectable from the dashboard's GPS **source** dropdown:
|
||||
|
||||
- **USB NMEA puck** plugged into the host running Flask — Flask reads NMEA and timestamps a GPS timeline
|
||||
- **Flask dashboard open in a phone browser** — browser Geolocation API posts updates to Flask
|
||||
- **Serial NMEA** — a USB NMEA puck plugged into the host running Flask; Flask reads NMEA at 9600 baud and timestamps a GPS timeline
|
||||
- **gpsd** — connect to a running `gpsd` daemon over TCP (default `localhost:2947`, host and port are configurable in the dashboard). Uses gpsd's line-delimited JSON protocol directly; no extra Python client library needed. Handy when several tools already share the same GPS via gpsd.
|
||||
- **Browser Geolocation** — the Flask dashboard open in a phone browser; the browser Geolocation API posts updates to Flask
|
||||
|
||||
Flask does a temporal match between detection timestamp and GPS timeline, then exports JSON / CSV / KML for Google Earth.
|
||||
|
||||
|
||||
+14
-2
@@ -52,12 +52,21 @@ A Flask-based web dashboard for real-time monitoring and analysis of Flock Safet
|
||||
5. **Export data** using the export buttons
|
||||
|
||||
### GPS Setup
|
||||
The GPS header row has a **source** dropdown with two options:
|
||||
|
||||
**Serial NMEA** (USB GPS dongle)
|
||||
1. **Connect GPS dongle** to your computer via USB
|
||||
2. **Select GPS port** from the dropdown in the header
|
||||
3. **Click "Connect"** to establish GPS connection
|
||||
4. **Monitor GPS status** via the status indicator
|
||||
5. **Detections will automatically include GPS data** when available
|
||||
|
||||
**gpsd** (shared GPS via the gpsd daemon)
|
||||
1. **Start gpsd** on the host (`gpsd -N /dev/ttyUSB0`, or any working `gpsd` setup)
|
||||
2. **Pick "gpsd"** from the source dropdown
|
||||
3. **Enter host and port** (default `localhost:2947`)
|
||||
4. **Click "Connect"** — the dashboard speaks gpsd's line-delimited JSON protocol directly, no extra Python client required. Handy when several tools already share the same GPS via gpsd.
|
||||
|
||||
### Data Export
|
||||
- **CSV Export**: Downloads a CSV file with all detection data
|
||||
- **KML Export**: Downloads a KML file for viewing in Google Earth
|
||||
@@ -72,8 +81,11 @@ A Flask-based web dashboard for real-time monitoring and analysis of Flock Safet
|
||||
|
||||
### GPS Management
|
||||
- `GET /api/gps/ports` - Get available serial ports
|
||||
- `POST /api/gps/connect` - Connect to GPS dongle
|
||||
- `POST /api/gps/disconnect` - Disconnect GPS dongle
|
||||
- `POST /api/gps/connect` - Connect a GPS source. Body:
|
||||
- `{"source": "serial", "port": "/dev/tty.usbserial-XXX"}` — opens an NMEA reader at 9600 baud
|
||||
- `{"source": "gpsd", "host": "localhost", "port": 2947}` — connects to a running gpsd daemon
|
||||
- Legacy body `{"port": "..."}` still works and is treated as serial
|
||||
- `POST /api/gps/disconnect` - Disconnect whichever GPS source is active
|
||||
|
||||
### Data Export
|
||||
- `GET /api/export/csv` - Export detections as CSV
|
||||
|
||||
+260
-53
@@ -8,6 +8,7 @@ from flask_socketio import SocketIO, emit, join_room, leave_room
|
||||
import threading
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import socket
|
||||
import queue
|
||||
import uuid
|
||||
import pickle
|
||||
@@ -38,7 +39,20 @@ reconnect_delay = 3 # seconds
|
||||
connection_lock = threading.Lock()
|
||||
serial_queue = queue.Queue()
|
||||
next_detection_id = 1 # Unique ID counter
|
||||
settings = {'gps_port': '', 'flock_port': '', 'filter': 'all'}
|
||||
settings = {
|
||||
'gps_source': 'serial', # 'serial' | 'gpsd'
|
||||
'gps_port': '', # serial device path
|
||||
'gpsd_host': 'localhost', # gpsd hostname or IP
|
||||
'gpsd_port': 2947, # gpsd TCP port
|
||||
'flock_port': '',
|
||||
'filter': 'all',
|
||||
}
|
||||
|
||||
# gpsd (source='gpsd') state — parallel to the serial GPS globals. Both
|
||||
# feed into the same `gps_data` / `gps_history` structure so the rest of
|
||||
# the pipeline is source-agnostic.
|
||||
gpsd_socket = None
|
||||
gps_source = None # 'serial' | 'gpsd' | None
|
||||
|
||||
# Data storage paths
|
||||
DATA_DIR = Path('data')
|
||||
@@ -245,6 +259,104 @@ def gps_reader():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
def parse_gpsd_tpv(msg, sat_count=0):
|
||||
"""Convert a gpsd TPV JSON object into the same dict shape as
|
||||
parse_nmea_sentence(). Returns None if the fix isn't usable."""
|
||||
if msg.get('class') != 'TPV':
|
||||
return None
|
||||
mode = msg.get('mode', 0) # 0=unknown, 1=no-fix, 2=2D, 3=3D
|
||||
if mode < 2:
|
||||
return None
|
||||
lat = msg.get('lat')
|
||||
lon = msg.get('lon')
|
||||
if lat is None or lon is None:
|
||||
return None
|
||||
alt = msg.get('altHAE')
|
||||
if alt is None:
|
||||
alt = msg.get('alt', 0) or 0
|
||||
return {
|
||||
'latitude': round(float(lat), 8),
|
||||
'longitude': round(float(lon), 8),
|
||||
'altitude': round(float(alt), 3),
|
||||
# gpsd mode 2 (2D) ≈ NMEA quality 1, mode 3 (3D) ≈ quality 2.
|
||||
'fix_quality': mode - 1,
|
||||
'satellites': int(sat_count) if sat_count else 0,
|
||||
'hdop': float(msg.get('hdop', 0) or 0),
|
||||
'timestamp': msg.get('time', ''),
|
||||
}
|
||||
|
||||
def gpsd_reader():
|
||||
"""Background thread that reads from a running gpsd instance and
|
||||
feeds gps_data / gps_history in the same shape as the serial NMEA
|
||||
reader. Speaks gpsd's line-delimited JSON protocol directly — no
|
||||
third-party gps client library needed."""
|
||||
global gps_data, gpsd_socket, gps_enabled
|
||||
|
||||
sat_count = 0
|
||||
buf = ''
|
||||
|
||||
while gps_enabled:
|
||||
sock = gpsd_socket
|
||||
if not sock:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
try:
|
||||
chunk = sock.recv(4096)
|
||||
if not chunk:
|
||||
raise ConnectionError('gpsd closed the socket')
|
||||
buf += chunk.decode('utf-8', errors='ignore')
|
||||
while '\n' in buf:
|
||||
line, buf = buf.split('\n', 1)
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
safe_socket_emit('serial_data', f"gpsd: {line}", room='serial_terminal')
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
cls = msg.get('class')
|
||||
if cls == 'SKY':
|
||||
sats = msg.get('satellites') or []
|
||||
sat_count = sum(1 for s in sats if s.get('used'))
|
||||
continue
|
||||
if cls != 'TPV':
|
||||
continue
|
||||
|
||||
parsed = parse_gpsd_tpv(msg, sat_count=sat_count)
|
||||
if not parsed:
|
||||
continue
|
||||
|
||||
gps_data = parsed
|
||||
if parsed.get('fix_quality', 0) > 0:
|
||||
gps_entry = parsed.copy()
|
||||
gps_entry['system_timestamp'] = time.time()
|
||||
gps_history.append(gps_entry)
|
||||
if len(gps_history) > MAX_GPS_HISTORY:
|
||||
gps_history.pop(0)
|
||||
|
||||
safe_socket_emit('gps_update', parsed)
|
||||
if parsed.get('fix_quality', 0) > 0:
|
||||
gps_info = (f"gpsd Fix: {parsed.get('latitude', 'N/A')}, "
|
||||
f"{parsed.get('longitude', 'N/A')} - "
|
||||
f"{parsed.get('satellites', 0)} satellites")
|
||||
safe_socket_emit('serial_data', gps_info, room='serial_terminal')
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"gpsd read error: {e}")
|
||||
with connection_lock:
|
||||
gps_enabled = False
|
||||
try:
|
||||
if gpsd_socket:
|
||||
gpsd_socket.close()
|
||||
except Exception:
|
||||
pass
|
||||
gpsd_socket = None
|
||||
safe_socket_emit('gps_disconnected', {})
|
||||
break
|
||||
|
||||
def flock_reader():
|
||||
"""Background thread for reading Flock device data"""
|
||||
global flock_serial_connection, flock_device_connected, serial_data_buffer
|
||||
@@ -599,43 +711,73 @@ def attempt_reconnect_flock():
|
||||
thread.start()
|
||||
|
||||
def attempt_reconnect_gps():
|
||||
"""Attempt to reconnect to GPS device"""
|
||||
global gps_enabled, reconnect_attempts
|
||||
|
||||
"""Attempt to reconnect whichever GPS source was in use. Only fires
|
||||
if the reader thread died — for a user-initiated disconnect the
|
||||
source is cleared and this exits immediately."""
|
||||
global gps_enabled, gpsd_socket, serial_connection, reconnect_attempts
|
||||
|
||||
prior_source = gps_source
|
||||
# Snapshot the endpoint from whichever source was live so we can
|
||||
# retry without depending on the (now-torn-down) object.
|
||||
prior_serial_port = serial_connection.port if serial_connection else None
|
||||
prior_gpsd_endpoint = None
|
||||
if gpsd_socket:
|
||||
try:
|
||||
prior_gpsd_endpoint = gpsd_socket.getpeername()[:2]
|
||||
except Exception:
|
||||
prior_gpsd_endpoint = None
|
||||
|
||||
if prior_source is None:
|
||||
return
|
||||
|
||||
def reconnect_thread():
|
||||
global gps_enabled, reconnect_attempts
|
||||
global gps_enabled, gpsd_socket, serial_connection, gps_source, reconnect_attempts
|
||||
|
||||
with app.app_context():
|
||||
while not gps_enabled and reconnect_attempts['gps'] < max_reconnect_attempts:
|
||||
try:
|
||||
print(f"Attempting to reconnect to GPS device (attempt {reconnect_attempts['gps'] + 1}/{max_reconnect_attempts})")
|
||||
|
||||
# Try to reconnect
|
||||
test_ser = serial.Serial(serial_connection.port, GPS_BAUDRATE, timeout=1)
|
||||
test_ser.close()
|
||||
|
||||
# If successful, update status
|
||||
with connection_lock:
|
||||
gps_enabled = True
|
||||
print(f"Attempting to reconnect GPS ({prior_source}) "
|
||||
f"attempt {reconnect_attempts['gps'] + 1}/{max_reconnect_attempts}")
|
||||
|
||||
if prior_source == 'serial' and prior_serial_port:
|
||||
serial_connection = serial.Serial(prior_serial_port, GPS_BAUDRATE,
|
||||
timeout=GPS_TIMEOUT)
|
||||
with connection_lock:
|
||||
gps_enabled = True
|
||||
gps_source = 'serial'
|
||||
threading.Thread(target=gps_reader, daemon=True).start()
|
||||
safe_socket_emit('gps_reconnected',
|
||||
{'source': 'serial', 'port': prior_serial_port})
|
||||
elif prior_source == 'gpsd' and prior_gpsd_endpoint:
|
||||
host, port = prior_gpsd_endpoint
|
||||
sock = socket.create_connection((host, port), timeout=5)
|
||||
sock.settimeout(2.0)
|
||||
sock.sendall(b'?WATCH={"enable":true,"json":true}\n')
|
||||
gpsd_socket = sock
|
||||
with connection_lock:
|
||||
gps_enabled = True
|
||||
gps_source = 'gpsd'
|
||||
threading.Thread(target=gpsd_reader, daemon=True).start()
|
||||
safe_socket_emit('gps_reconnected',
|
||||
{'source': 'gpsd', 'endpoint': f'{host}:{port}'})
|
||||
else:
|
||||
print(f"Cannot reconnect: no endpoint recorded for {prior_source}")
|
||||
break
|
||||
|
||||
reconnect_attempts['gps'] = 0
|
||||
print(f"Successfully reconnected to GPS device on {serial_connection.port}")
|
||||
safe_socket_emit('gps_reconnected', {'port': serial_connection.port})
|
||||
|
||||
# Restart the reading thread
|
||||
gps_thread = threading.Thread(target=gps_reader, daemon=True)
|
||||
gps_thread.start()
|
||||
print(f"Successfully reconnected GPS ({prior_source})")
|
||||
return
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"GPS reconnection attempt failed: {e}")
|
||||
reconnect_attempts['gps'] += 1
|
||||
time.sleep(reconnect_delay)
|
||||
|
||||
|
||||
if reconnect_attempts['gps'] >= max_reconnect_attempts:
|
||||
print("Max reconnection attempts reached for GPS device")
|
||||
safe_socket_emit('reconnect_failed', {'device': 'gps'})
|
||||
reconnect_attempts['gps'] = 0 # Reset for future attempts
|
||||
|
||||
reconnect_attempts['gps'] = 0
|
||||
|
||||
thread = threading.Thread(target=reconnect_thread, daemon=True)
|
||||
thread.start()
|
||||
|
||||
@@ -695,39 +837,95 @@ def add_detection():
|
||||
|
||||
@app.route('/api/gps/connect', methods=['POST'])
|
||||
def connect_gps():
|
||||
"""Connect to GPS dongle"""
|
||||
global serial_connection, gps_enabled
|
||||
|
||||
data = request.json
|
||||
port = data.get('port')
|
||||
|
||||
try:
|
||||
if serial_connection:
|
||||
"""Connect a GPS source.
|
||||
|
||||
Request body:
|
||||
{"source": "serial", "port": "/dev/tty.usbserial-XXX"}
|
||||
{"source": "gpsd", "host": "localhost", "port": 2947}
|
||||
|
||||
Legacy body {"port": "..."} still works and is treated as serial.
|
||||
"""
|
||||
global serial_connection, gpsd_socket, gps_enabled, gps_source
|
||||
|
||||
data = request.json or {}
|
||||
source = (data.get('source') or 'serial').lower()
|
||||
|
||||
if source == 'serial':
|
||||
port = data.get('port')
|
||||
if not port:
|
||||
return jsonify({'status': 'error', 'message': 'Serial port is required'}), 400
|
||||
try:
|
||||
_teardown_gps()
|
||||
serial_connection = serial.Serial(port, GPS_BAUDRATE, timeout=GPS_TIMEOUT)
|
||||
with connection_lock:
|
||||
gps_enabled = True
|
||||
gps_source = 'serial'
|
||||
threading.Thread(target=gps_reader, daemon=True).start()
|
||||
return jsonify({'status': 'success', 'source': 'serial',
|
||||
'message': f'Connected to {port}'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
|
||||
if source == 'gpsd':
|
||||
host = data.get('host') or 'localhost'
|
||||
# Accept either "port" or "gpsd_port" — the frontend uses the
|
||||
# same field name for both sources but the settings key is
|
||||
# gpsd_port, so let both work.
|
||||
port = data.get('port') or data.get('gpsd_port') or 2947
|
||||
try:
|
||||
port = int(port)
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({'status': 'error', 'message': f'Bad gpsd port: {port}'}), 400
|
||||
try:
|
||||
_teardown_gps()
|
||||
sock = socket.create_connection((host, port), timeout=5)
|
||||
sock.settimeout(2.0)
|
||||
# Ask gpsd to start streaming JSON reports.
|
||||
sock.sendall(b'?WATCH={"enable":true,"json":true}\n')
|
||||
gpsd_socket = sock
|
||||
with connection_lock:
|
||||
gps_enabled = True
|
||||
gps_source = 'gpsd'
|
||||
threading.Thread(target=gpsd_reader, daemon=True).start()
|
||||
return jsonify({'status': 'success', 'source': 'gpsd',
|
||||
'message': f'Connected to gpsd at {host}:{port}'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error',
|
||||
'message': f'gpsd connect failed ({host}:{port}): {e}'}), 400
|
||||
|
||||
return jsonify({'status': 'error', 'message': f'Unknown GPS source: {source}'}), 400
|
||||
|
||||
|
||||
def _teardown_gps():
|
||||
"""Close whichever GPS source is currently active. Called on
|
||||
reconnect and disconnect so the reader threads see gps_enabled go
|
||||
False and exit cleanly."""
|
||||
global serial_connection, gpsd_socket, gps_enabled, gps_source
|
||||
with connection_lock:
|
||||
gps_enabled = False
|
||||
gps_source = None
|
||||
if serial_connection:
|
||||
try:
|
||||
serial_connection.close()
|
||||
|
||||
serial_connection = serial.Serial(port, GPS_BAUDRATE, timeout=GPS_TIMEOUT)
|
||||
with connection_lock:
|
||||
gps_enabled = True
|
||||
|
||||
# Start GPS reading thread
|
||||
gps_thread = threading.Thread(target=gps_reader, daemon=True)
|
||||
gps_thread.start()
|
||||
|
||||
return jsonify({'status': 'success', 'message': f'Connected to {port}'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
except Exception:
|
||||
pass
|
||||
serial_connection = None
|
||||
if gpsd_socket:
|
||||
try:
|
||||
gpsd_socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
gpsd_socket.close()
|
||||
except Exception:
|
||||
pass
|
||||
gpsd_socket = None
|
||||
|
||||
|
||||
@app.route('/api/gps/disconnect', methods=['POST'])
|
||||
def disconnect_gps():
|
||||
"""Disconnect GPS dongle"""
|
||||
global serial_connection, gps_enabled
|
||||
|
||||
with connection_lock:
|
||||
gps_enabled = False
|
||||
if serial_connection:
|
||||
serial_connection.close()
|
||||
serial_connection = None
|
||||
|
||||
"""Disconnect whichever GPS source is currently active."""
|
||||
_teardown_gps()
|
||||
return jsonify({'status': 'success', 'message': 'GPS disconnected'})
|
||||
|
||||
@app.route('/api/flock/connect', methods=['POST'])
|
||||
@@ -771,9 +969,18 @@ def disconnect_flock():
|
||||
@app.route('/api/status', methods=['GET'])
|
||||
def get_status():
|
||||
"""Get connection status of both devices"""
|
||||
gpsd_endpoint = None
|
||||
if gpsd_socket:
|
||||
try:
|
||||
host, port = gpsd_socket.getpeername()[:2]
|
||||
gpsd_endpoint = f'{host}:{port}'
|
||||
except Exception:
|
||||
gpsd_endpoint = None
|
||||
return jsonify({
|
||||
'gps_connected': gps_enabled,
|
||||
'gps_source': gps_source,
|
||||
'gps_port': serial_connection.port if serial_connection else None,
|
||||
'gpsd_endpoint': gpsd_endpoint,
|
||||
'flock_connected': flock_device_connected,
|
||||
'flock_port': flock_device_port
|
||||
})
|
||||
|
||||
+62
-11
@@ -1303,12 +1303,20 @@
|
||||
<div class="device-control-group">
|
||||
<div class="status-indicator" id="gpsStatus"></div>
|
||||
<label>GPS:</label>
|
||||
<div class="port-controls">
|
||||
<select id="gpsSourceSelect" title="GPS source">
|
||||
<option value="serial">Serial NMEA</option>
|
||||
<option value="gpsd">gpsd</option>
|
||||
</select>
|
||||
<div class="port-controls" id="gpsSerialControls">
|
||||
<select id="gpsPortSelect">
|
||||
<option value="">Select Port</option>
|
||||
</select>
|
||||
<button id="refreshGpsPortsBtn" class="refresh-btn" title="Refresh ports" onclick="loadGpsPorts()">↻</button>
|
||||
</div>
|
||||
<div class="port-controls" id="gpsdControls" style="display: none;">
|
||||
<input type="text" id="gpsdHostInput" placeholder="host" value="localhost" style="width: 8em;">
|
||||
<input type="number" id="gpsdPortInput" placeholder="port" value="2947" min="1" max="65535" style="width: 5em;">
|
||||
</div>
|
||||
<button id="connectGpsBtn">Connect</button>
|
||||
<button id="disconnectGpsBtn" class="danger" style="display: none;">Disconnect</button>
|
||||
</div>
|
||||
@@ -1556,6 +1564,15 @@
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
document.getElementById('gpsSourceSelect').addEventListener('change', function() {
|
||||
applyGpsSource(this.value);
|
||||
saveSettings();
|
||||
});
|
||||
document.getElementById('gpsdHostInput').addEventListener('change', saveSettings);
|
||||
document.getElementById('gpsdPortInput').addEventListener('change', saveSettings);
|
||||
// Initial visibility pass — reflect whatever the dropdown starts at.
|
||||
applyGpsSource(document.getElementById('gpsSourceSelect').value);
|
||||
|
||||
document.getElementById('connectFlockBtn').addEventListener('click', connectFlock);
|
||||
document.getElementById('disconnectFlockBtn').addEventListener('click', disconnectFlock);
|
||||
document.getElementById('connectGpsBtn').addEventListener('click', connectGps);
|
||||
@@ -1592,16 +1609,32 @@
|
||||
});
|
||||
}
|
||||
|
||||
function applyGpsSource(source) {
|
||||
const isGpsd = (source === 'gpsd');
|
||||
document.getElementById('gpsSerialControls').style.display = isGpsd ? 'none' : '';
|
||||
document.getElementById('gpsdControls').style.display = isGpsd ? '' : 'none';
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
fetch('/api/settings')
|
||||
.then(response => response.json())
|
||||
.then(settings => {
|
||||
console.log('Loaded settings:', settings);
|
||||
|
||||
|
||||
// Apply settings to UI
|
||||
if (settings.gps_source) {
|
||||
document.getElementById('gpsSourceSelect').value = settings.gps_source;
|
||||
applyGpsSource(settings.gps_source);
|
||||
}
|
||||
if (settings.gps_port) {
|
||||
document.getElementById('gpsPortSelect').value = settings.gps_port;
|
||||
}
|
||||
if (settings.gpsd_host) {
|
||||
document.getElementById('gpsdHostInput').value = settings.gpsd_host;
|
||||
}
|
||||
if (settings.gpsd_port) {
|
||||
document.getElementById('gpsdPortInput').value = settings.gpsd_port;
|
||||
}
|
||||
if (settings.flock_port) {
|
||||
document.getElementById('flockDeviceSelect').value = settings.flock_port;
|
||||
}
|
||||
@@ -1617,7 +1650,10 @@
|
||||
|
||||
function saveSettings() {
|
||||
const settings = {
|
||||
gps_source: document.getElementById('gpsSourceSelect').value,
|
||||
gps_port: document.getElementById('gpsPortSelect').value,
|
||||
gpsd_host: document.getElementById('gpsdHostInput').value,
|
||||
gpsd_port: parseInt(document.getElementById('gpsdPortInput').value, 10) || 2947,
|
||||
flock_port: document.getElementById('flockDeviceSelect').value,
|
||||
filter: document.getElementById('filterSelect').value
|
||||
};
|
||||
@@ -1784,20 +1820,35 @@
|
||||
}
|
||||
|
||||
function connectGps() {
|
||||
const port = document.getElementById('gpsPortSelect').value;
|
||||
if (!port) {
|
||||
alert('Please select a GPS port');
|
||||
const source = document.getElementById('gpsSourceSelect').value || 'serial';
|
||||
const body = { source: source };
|
||||
let label;
|
||||
|
||||
if (source === 'serial') {
|
||||
const port = document.getElementById('gpsPortSelect').value;
|
||||
if (!port) {
|
||||
alert('Please select a GPS serial port');
|
||||
return;
|
||||
}
|
||||
body.port = port;
|
||||
label = `serial ${port}`;
|
||||
} else if (source === 'gpsd') {
|
||||
const host = (document.getElementById('gpsdHostInput').value || 'localhost').trim();
|
||||
const port = parseInt(document.getElementById('gpsdPortInput').value, 10) || 2947;
|
||||
body.host = host;
|
||||
body.port = port;
|
||||
label = `gpsd ${host}:${port}`;
|
||||
} else {
|
||||
alert('Unknown GPS source: ' + source);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Connecting to GPS device on port:', port);
|
||||
|
||||
console.log('Connecting GPS:', label);
|
||||
|
||||
fetch('/api/gps/connect', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ port: port })
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
|
||||
Reference in New Issue
Block a user