gps manipulations tailored to sim7600h hat
This commit is contained in:
@@ -178,6 +178,19 @@ The Pi's internal pull-down (~50kΩ) will overpower high-value external resistor
|
|||||||
|
|
||||||
Physical switches/connectors need debouncing. Current implementation requires 15 consecutive identical readings (~750ms at 20Hz) before accepting a state change. Tune `required_consecutive` in `gpio_service.py` as needed.
|
Physical switches/connectors need debouncing. Current implementation requires 15 consecutive identical readings (~750ms at 20Hz) before accepting a state change. Tune `required_consecutive` in `gpio_service.py` as needed.
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
Standalone tools live in `pi/utils/` (not part of the backend service):
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `at_terminal.py` | Interactive AT command terminal for SIM7600 (pyserial). Default port: `/dev/ttyUSB2` |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python pi/utils/at_terminal.py # default /dev/ttyUSB2
|
||||||
|
python pi/utils/at_terminal.py /dev/ttyUSB3 # specify port
|
||||||
|
```
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
TODO: Add to `scripts/deploy.py` as second target + systemd service.
|
TODO: Add to `scripts/deploy.py` as second target + systemd service.
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ class GPSService:
|
|||||||
# Callback for push-based updates
|
# Callback for push-based updates
|
||||||
self._on_data_callback = None
|
self._on_data_callback = None
|
||||||
|
|
||||||
|
# GPS state tracking (NMEA can't distinguish "acquiring" from "lost")
|
||||||
|
self._has_ever_fixed = False # True after first valid fix this session
|
||||||
|
|
||||||
# Periodic status logging
|
# Periodic status logging
|
||||||
self._last_status_log = 0.0
|
self._last_status_log = 0.0
|
||||||
self._fix_count = 0
|
self._fix_count = 0
|
||||||
@@ -57,6 +60,17 @@ class GPSService:
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
return self._latest.copy() if self._latest else {"error": "no data"}
|
return self._latest.copy() if self._latest else {"error": "no data"}
|
||||||
|
|
||||||
|
def _gps_state(self, fix: dict) -> str:
|
||||||
|
"""Determine GPS state: acquiring, fix, or lost.
|
||||||
|
|
||||||
|
NMEA doesn't distinguish 'never had fix' from 'lost signal' — both
|
||||||
|
report mode 1 with no position. We track it ourselves.
|
||||||
|
"""
|
||||||
|
has_fix = fix.get("mode") in (2, 3) and fix.get("lat") is not None
|
||||||
|
if has_fix:
|
||||||
|
return "fix"
|
||||||
|
return "lost" if self._has_ever_fixed else "acquiring"
|
||||||
|
|
||||||
def get_buffer(self) -> list[dict[str, Any]]:
|
def get_buffer(self) -> list[dict[str, Any]]:
|
||||||
"""Get buffered GPS history."""
|
"""Get buffered GPS history."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
@@ -122,7 +136,8 @@ class GPSService:
|
|||||||
|
|
||||||
self._last_status_log = time.time()
|
self._last_status_log = time.time()
|
||||||
self._fix_count = 0
|
self._fix_count = 0
|
||||||
first_fix_timeout = time.time() + 5.0 # 5s to get first fix
|
# 120s for initial cold fix, 10s for signal loss after first fix
|
||||||
|
fix_timeout = time.time() + 120.0
|
||||||
|
|
||||||
for result in client.dict_stream(filter=["TPV"]):
|
for result in client.dict_stream(filter=["TPV"]):
|
||||||
if not self._running:
|
if not self._running:
|
||||||
@@ -140,16 +155,23 @@ class GPSService:
|
|||||||
"satellites": result.get("satellites"), # from SKY messages
|
"satellites": result.get("satellites"), # from SKY messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Compute state and attach to fix
|
||||||
|
fix["gps_state"] = self._gps_state(fix)
|
||||||
|
|
||||||
# Check if this is a real fix (has position) or just empty TPV
|
# Check if this is a real fix (has position) or just empty TPV
|
||||||
if fix.get("lat") is None and fix.get("mode") in (None, 0, 1):
|
if fix.get("lat") is None and fix.get("mode") in (None, 0, 1):
|
||||||
# No real data yet, check timeout
|
# No real data yet, check timeout
|
||||||
if time.time() > first_fix_timeout:
|
if time.time() > fix_timeout:
|
||||||
print("[GPS] No GPS fix after 5s, will retry connection")
|
timeout_s = "120s" if not self._has_ever_fixed else "10s"
|
||||||
|
print(f"[GPS] No GPS fix after {timeout_s}, will retry connection")
|
||||||
raise ConnectionError("No GPS fix within timeout")
|
raise ConnectionError("No GPS fix within timeout")
|
||||||
continue # Skip empty fixes
|
continue # Skip empty fixes
|
||||||
|
|
||||||
# Got real data, disable timeout
|
# Got real data — mark first fix, reset timeout to shorter window
|
||||||
first_fix_timeout = float('inf')
|
if not self._has_ever_fixed:
|
||||||
|
self._has_ever_fixed = True
|
||||||
|
print("[GPS] First fix acquired")
|
||||||
|
fix_timeout = time.time() + 10.0 # 10s timeout for signal loss
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._latest = fix
|
self._latest = fix
|
||||||
@@ -178,8 +200,9 @@ class GPSService:
|
|||||||
"""Generate realistic mock GPS data for development/testing.
|
"""Generate realistic mock GPS data for development/testing.
|
||||||
|
|
||||||
Simulates:
|
Simulates:
|
||||||
|
- Initial acquisition delay (~3s before first fix)
|
||||||
- Normal 3D fix with satellites
|
- Normal 3D fix with satellites
|
||||||
- Occasional signal loss (~3% chance per second, lasts ~5s)
|
- Occasional signal loss (~30% chance per second, lasts ~2s)
|
||||||
- Wandering position near Tokyo
|
- Wandering position near Tokyo
|
||||||
"""
|
"""
|
||||||
self._last_status_log = time.time()
|
self._last_status_log = time.time()
|
||||||
@@ -189,6 +212,9 @@ class GPSService:
|
|||||||
signal_lost = False
|
signal_lost = False
|
||||||
signal_lost_until = 0.0
|
signal_lost_until = 0.0
|
||||||
|
|
||||||
|
# Simulate cold start acquisition (~3s)
|
||||||
|
acquiring_until = time.time() + 3.0
|
||||||
|
|
||||||
# Base position (Tokyo area)
|
# Base position (Tokyo area)
|
||||||
base_lat = 35.6762
|
base_lat = 35.6762
|
||||||
base_lon = 139.6503
|
base_lon = 139.6503
|
||||||
@@ -202,16 +228,20 @@ class GPSService:
|
|||||||
self._connected = True
|
self._connected = True
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
# Check for signal loss simulation
|
# Simulate initial acquisition period
|
||||||
if signal_lost:
|
if now < acquiring_until:
|
||||||
if now >= signal_lost_until:
|
signal_lost = True # No fix yet
|
||||||
signal_lost = False
|
elif signal_lost and now >= signal_lost_until:
|
||||||
|
signal_lost = False
|
||||||
|
if self._has_ever_fixed:
|
||||||
print("[GPS] Signal recovered (stub)")
|
print("[GPS] Signal recovered (stub)")
|
||||||
else:
|
else:
|
||||||
|
print("[GPS] First fix acquired (stub)")
|
||||||
|
elif not signal_lost:
|
||||||
# ~30% chance per second to lose signal
|
# ~30% chance per second to lose signal
|
||||||
if random.random() < 0.3:
|
if random.random() < 0.3:
|
||||||
signal_lost = True
|
signal_lost = True
|
||||||
signal_lost_until = now + 2 # fixed 2s loss
|
signal_lost_until = now + 2 # fixed 2s loss
|
||||||
print("[GPS] Signal loss simulation (stub)")
|
print("[GPS] Signal loss simulation (stub)")
|
||||||
|
|
||||||
if signal_lost:
|
if signal_lost:
|
||||||
@@ -232,6 +262,9 @@ class GPSService:
|
|||||||
heading = (heading + random.uniform(1, 3)) % 360
|
heading = (heading + random.uniform(1, 3)) % 360
|
||||||
speed = max(0, min(30, speed + random.uniform(-2, 2)))
|
speed = max(0, min(30, speed + random.uniform(-2, 2)))
|
||||||
|
|
||||||
|
if not self._has_ever_fixed:
|
||||||
|
self._has_ever_fixed = True
|
||||||
|
|
||||||
fix = {
|
fix = {
|
||||||
"time": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
"time": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
"lat": base_lat + random.uniform(-0.001, 0.001),
|
"lat": base_lat + random.uniform(-0.001, 0.001),
|
||||||
@@ -243,6 +276,9 @@ class GPSService:
|
|||||||
"satellites": random.randint(6, 12),
|
"satellites": random.randint(6, 12),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Attach state — same logic as real GPS path
|
||||||
|
fix["gps_state"] = self._gps_state(fix)
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._latest = fix
|
self._latest = fix
|
||||||
if fix.get("lat") is not None:
|
if fix.get("lat") is not None:
|
||||||
|
|||||||
@@ -180,9 +180,11 @@ def throttle_flusher():
|
|||||||
@app.route("/health")
|
@app.route("/health")
|
||||||
def health():
|
def health():
|
||||||
"""Health check endpoint."""
|
"""Health check endpoint."""
|
||||||
|
gps_latest = gps.get_latest()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"gps_connected": gps.connected,
|
"gps_connected": gps.connected,
|
||||||
|
"gps_state": gps_latest.get("gps_state", "acquiring"),
|
||||||
"arduino_connected": arduino.connected,
|
"arduino_connected": arduino.connected,
|
||||||
"ws_clients": len(connected_clients),
|
"ws_clients": len(connected_clients),
|
||||||
})
|
})
|
||||||
|
|||||||
49
pi/backend/utils/at_terminal.py
Normal file
49
pi/backend/utils/at_terminal.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""Quick AT command terminal - minicom but less hostile.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python at_terminal.py [port]
|
||||||
|
|
||||||
|
Default port: /dev/ttyUSB2 (SIM7600 AT command interface)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import serial
|
||||||
|
import threading
|
||||||
|
|
||||||
|
PORT = sys.argv[1] if len(sys.argv) > 1 else "/dev/ttyUSB2"
|
||||||
|
BAUD = 115200
|
||||||
|
|
||||||
|
|
||||||
|
def reader(ser):
|
||||||
|
"""Background thread: print everything the modem sends."""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data = ser.read(ser.in_waiting or 1)
|
||||||
|
if data:
|
||||||
|
sys.stdout.write(data.decode("utf-8", errors="replace"))
|
||||||
|
sys.stdout.flush()
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(f"Opening {PORT} @ {BAUD} baud")
|
||||||
|
print("Type AT commands. Ctrl+C to quit.\n")
|
||||||
|
|
||||||
|
ser = serial.Serial(PORT, BAUD, timeout=0.1)
|
||||||
|
|
||||||
|
t = threading.Thread(target=reader, args=(ser,), daemon=True)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
line = input()
|
||||||
|
ser.write((line + "\r\n").encode())
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
print("\nBye.")
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -41,6 +41,7 @@ class _AppRootState extends State<AppRoot> {
|
|||||||
// Show all items from the start so the row doesn't jump around
|
// Show all items from the start so the row doesn't jump around
|
||||||
_updateStatus('Config', '...');
|
_updateStatus('Config', '...');
|
||||||
_updateStatus('UART', '...');
|
_updateStatus('UART', '...');
|
||||||
|
_updateStatus('GPS', '...');
|
||||||
_updateStatus('Navigator', '...');
|
_updateStatus('Navigator', '...');
|
||||||
|
|
||||||
// Config must load first (everything else depends on it)
|
// Config must load first (everything else depends on it)
|
||||||
@@ -48,11 +49,13 @@ class _AppRootState extends State<AppRoot> {
|
|||||||
await ConfigService.instance.load();
|
await ConfigService.instance.load();
|
||||||
_updateStatus('Config', 'Ready');
|
_updateStatus('Config', 'Ready');
|
||||||
|
|
||||||
// UART health check and navigator image preload run truly in parallel
|
// UART, GPS, and navigator image preload run truly in parallel
|
||||||
_updateStatus('UART', 'Connecting');
|
_updateStatus('UART', 'Connecting');
|
||||||
|
_updateStatus('GPS', 'Waiting');
|
||||||
_updateStatus('Navigator', 'Loading');
|
_updateStatus('Navigator', 'Loading');
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_waitForUart(),
|
_waitForUart(),
|
||||||
|
_waitForGps(),
|
||||||
_preloadNavigatorImages(),
|
_preloadNavigatorImages(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -100,6 +103,39 @@ class _AppRootState extends State<AppRoot> {
|
|||||||
_updateStatus('UART', 'Timeout');
|
_updateStatus('UART', 'Timeout');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Poll backend health endpoint until GPS has a fix, or bail after 7.5s
|
||||||
|
Future<void> _waitForGps() async {
|
||||||
|
final backendUrl = ConfigService.instance.backendUrl;
|
||||||
|
const bailOut = Duration(milliseconds: 7500);
|
||||||
|
const retryDelay = Duration(seconds: 1);
|
||||||
|
final deadline = DateTime.now().add(bailOut);
|
||||||
|
|
||||||
|
_updateStatus('GPS', 'Acquiring');
|
||||||
|
|
||||||
|
while (DateTime.now().isBefore(deadline)) {
|
||||||
|
try {
|
||||||
|
final response = await http
|
||||||
|
.get(Uri.parse('$backendUrl/health'))
|
||||||
|
.timeout(const Duration(seconds: 2));
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||||
|
if (data['gps_state'] == 'fix') {
|
||||||
|
_updateStatus('GPS', 'Ready');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Backend not reachable yet - keep trying
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.delayed(retryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bail out - dashboard will show live GPS state when it arrives
|
||||||
|
_updateStatus('GPS', 'Timeout');
|
||||||
|
}
|
||||||
|
|
||||||
/// Preload navigator images into Flutter's image cache
|
/// Preload navigator images into Flutter's image cache
|
||||||
///
|
///
|
||||||
/// Scans for all PNGs in the navigator folder and precaches them.
|
/// Scans for all PNGs in the navigator folder and precaches them.
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
|
|
||||||
// Placeholder values for system bar
|
// Placeholder values for system bar
|
||||||
int? _gpsSatellites;
|
int? _gpsSatellites;
|
||||||
|
String? _gpsState;
|
||||||
int? _lteSignal;
|
int? _lteSignal;
|
||||||
|
|
||||||
// WebSocket connection state
|
// WebSocket connection state
|
||||||
@@ -109,6 +110,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
_gpsSpeed = data.speed;
|
_gpsSpeed = data.speed;
|
||||||
_gpsTrack = data.track;
|
_gpsTrack = data.track;
|
||||||
_gpsSatellites = data.satellites;
|
_gpsSatellites = data.satellites;
|
||||||
|
_gpsState = data.gpsState;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -144,6 +146,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
_gpsSpeed = cachedGps.speed;
|
_gpsSpeed = cachedGps.speed;
|
||||||
_gpsTrack = cachedGps.track;
|
_gpsTrack = cachedGps.track;
|
||||||
_gpsSatellites = cachedGps.satellites;
|
_gpsSatellites = cachedGps.satellites;
|
||||||
|
_gpsState = cachedGps.gpsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
_wsState = WebSocketService.instance.connectionState;
|
_wsState = WebSocketService.instance.connectionState;
|
||||||
@@ -200,6 +203,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
// System status bar
|
// System status bar
|
||||||
SystemBar(
|
SystemBar(
|
||||||
gpsSatellites: _gpsSatellites,
|
gpsSatellites: _gpsSatellites,
|
||||||
|
gpsState: _gpsState,
|
||||||
lteSignal: _lteSignal,
|
lteSignal: _lteSignal,
|
||||||
piTemp: _piTemp,
|
piTemp: _piTemp,
|
||||||
voltage: _voltage,
|
voltage: _voltage,
|
||||||
@@ -237,7 +241,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
StatBox(value: _formatInt(_rpm), label: 'RPM', isWarning: () => (_rpm ?? 0) > 4000),
|
StatBox(value: _formatInt(_rpm), label: 'RPM', isWarning: () => (_rpm ?? 0) > 4000),
|
||||||
GpsCompass(heading: _gpsTrack),
|
GpsCompass(heading: _gpsTrack, gpsState: _gpsState),
|
||||||
StatBox(value: _formatGear(_gear), label: 'GEAR'),
|
StatBox(value: _formatGear(_gear), label: 'GEAR'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ class GpsData {
|
|||||||
final double? track;
|
final double? track;
|
||||||
final int? mode; // 0=no fix, 2=2D, 3=3D
|
final int? mode; // 0=no fix, 2=2D, 3=3D
|
||||||
final int? satellites;
|
final int? satellites;
|
||||||
|
final String? gpsState; // "acquiring", "fix", or "lost"
|
||||||
|
|
||||||
GpsData({this.lat, this.lon, this.speed, this.alt, this.track, this.mode, this.satellites});
|
GpsData({this.lat, this.lon, this.speed, this.alt, this.track, this.mode, this.satellites, this.gpsState});
|
||||||
|
|
||||||
factory GpsData.fromJson(Map<String, dynamic> json) {
|
factory GpsData.fromJson(Map<String, dynamic> json) {
|
||||||
return GpsData(
|
return GpsData(
|
||||||
@@ -52,6 +53,7 @@ class GpsData {
|
|||||||
track: (json['track'] as num?)?.toDouble(),
|
track: (json['track'] as num?)?.toDouble(),
|
||||||
mode: (json['mode'] as num?)?.toInt(),
|
mode: (json['mode'] as num?)?.toInt(),
|
||||||
satellites: (json['satellites'] as num?)?.toInt(),
|
satellites: (json['satellites'] as num?)?.toInt(),
|
||||||
|
gpsState: json['gps_state'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import '../theme/app_theme.dart';
|
|||||||
|
|
||||||
class GpsCompass extends StatelessWidget {
|
class GpsCompass extends StatelessWidget {
|
||||||
final double? heading;
|
final double? heading;
|
||||||
|
final String? gpsState; // "acquiring", "fix", "lost"
|
||||||
|
|
||||||
const GpsCompass({super.key, this.heading});
|
const GpsCompass({super.key, this.heading, this.gpsState});
|
||||||
|
|
||||||
bool get _hasSignal => heading != null;
|
bool get _hasSignal => heading != null;
|
||||||
|
bool get _isAcquiring => gpsState == 'acquiring';
|
||||||
|
|
||||||
String get _displayHeading {
|
String get _displayHeading {
|
||||||
if (!_hasSignal) return 'N/A'; // Just make it clear; redundant anyways, this only gets called when _hasSignal
|
if (!_hasSignal) return 'N/A';
|
||||||
return '${(heading! % 360).round()}'; // No need for the degree symbol
|
return '${(heading! % 360).round()}';
|
||||||
}
|
}
|
||||||
|
|
||||||
String get _compassDirection {
|
String get _compassDirection {
|
||||||
@@ -56,7 +58,7 @@ class GpsCompass extends StatelessWidget {
|
|||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
child: Text(
|
child: Text(
|
||||||
_hasSignal ? "${_displayHeading} ${_compassDirection}" : "N/A",
|
_hasSignal ? "${_displayHeading} ${_compassDirection}" : (_isAcquiring ? "ACQ" : "N/A"),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 80,
|
fontSize: 80,
|
||||||
color: theme.subdued,
|
color: theme.subdued,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../theme/app_theme.dart';
|
|||||||
/// Shows GPS satellites, LTE signal, Pi temp, voltage, WS status at a glance.
|
/// Shows GPS satellites, LTE signal, Pi temp, voltage, WS status at a glance.
|
||||||
class SystemBar extends StatelessWidget {
|
class SystemBar extends StatelessWidget {
|
||||||
final int? gpsSatellites; // null = disconnected
|
final int? gpsSatellites; // null = disconnected
|
||||||
|
final String? gpsState; // "acquiring", "fix", "lost"
|
||||||
final int? lteSignal; // null = disconnected, 0-4 bars
|
final int? lteSignal; // null = disconnected, 0-4 bars
|
||||||
final double? piTemp; // null = unavailable
|
final double? piTemp; // null = unavailable
|
||||||
final double? voltage; // null = Arduino disconnected
|
final double? voltage; // null = Arduino disconnected
|
||||||
@@ -15,6 +16,7 @@ class SystemBar extends StatelessWidget {
|
|||||||
const SystemBar({
|
const SystemBar({
|
||||||
super.key,
|
super.key,
|
||||||
this.gpsSatellites,
|
this.gpsSatellites,
|
||||||
|
this.gpsState,
|
||||||
this.lteSignal,
|
this.lteSignal,
|
||||||
this.piTemp,
|
this.piTemp,
|
||||||
this.voltage,
|
this.voltage,
|
||||||
@@ -65,8 +67,10 @@ class SystemBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
_Indicator(
|
_Indicator(
|
||||||
label: 'GPS',
|
label: 'GPS',
|
||||||
value: gpsSatellites?.toString() ?? 'N/A',
|
value: gpsState == 'acquiring' ? 'ACQ'
|
||||||
isAbnormal: gpsSatellites == null || gpsSatellites == 0,
|
: gpsState == 'fix' ? (gpsSatellites?.toString() ?? 'N/A')
|
||||||
|
: '0', // lost or unknown
|
||||||
|
isAbnormal: gpsState != 'fix',
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
labelSize: labelSize,
|
labelSize: labelSize,
|
||||||
valueSize: valueSize,
|
valueSize: valueSize,
|
||||||
|
|||||||
Reference in New Issue
Block a user