mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Update Cayenne LPP GPS encoding/decoding to use 3-byte signed big-endian format
This commit is contained in:
@@ -148,19 +148,33 @@ class CayenneLppParser {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppGps:
|
case MeshCoreConstants.lppGps:
|
||||||
// MeshCore GPS format: int32 LE (4 bytes each) × 10000 for lat/lon, × 100 for alt
|
// Standard Cayenne LPP GPS format (type 0x88):
|
||||||
// See MESHCORE_BLE_PROTOCOL.md lines 336-337, 434-435, 977-978
|
// - Latitude: 3 bytes, signed 24-bit, big-endian, × 10000
|
||||||
final rawLat = reader.readInt32LE();
|
// - Longitude: 3 bytes, signed 24-bit, big-endian, × 10000
|
||||||
final rawLon = reader.readInt32LE();
|
// - Altitude: 3 bytes, signed 24-bit, big-endian, × 100
|
||||||
final rawAlt = reader.readInt32LE();
|
// Total: 9 bytes (not the 12 bytes used in MeshCore advertisements!)
|
||||||
|
|
||||||
|
// Read 3-byte signed big-endian integers
|
||||||
|
final latBytes = reader.readBytes(3);
|
||||||
|
int rawLat = (latBytes[0] << 16) | (latBytes[1] << 8) | latBytes[2];
|
||||||
|
// Sign extend from 24-bit to 32-bit
|
||||||
|
if (rawLat > 0x7FFFFF) rawLat = rawLat - 0x1000000;
|
||||||
|
|
||||||
|
final lonBytes = reader.readBytes(3);
|
||||||
|
int rawLon = (lonBytes[0] << 16) | (lonBytes[1] << 8) | lonBytes[2];
|
||||||
|
if (rawLon > 0x7FFFFF) rawLon = rawLon - 0x1000000;
|
||||||
|
|
||||||
|
final altBytes = reader.readBytes(3);
|
||||||
|
int rawAlt = (altBytes[0] << 16) | (altBytes[1] << 8) | altBytes[2];
|
||||||
|
if (rawAlt > 0x7FFFFF) rawAlt = rawAlt - 0x1000000;
|
||||||
|
|
||||||
// Decode: divide by scaling factors
|
// Decode: divide by scaling factors
|
||||||
final lat = rawLat / 10000.0; // Fixed: was 1000000.0 (100x error!)
|
final lat = rawLat / 10000.0;
|
||||||
final lon = rawLon / 10000.0; // Fixed: was 1000000.0 (100x error!)
|
final lon = rawLon / 10000.0;
|
||||||
final alt = rawAlt / 100.0;
|
final alt = rawAlt / 100.0;
|
||||||
|
|
||||||
debugPrint(
|
debugPrint(
|
||||||
' GPS Location (raw int32 LE): lat=$rawLat (0x${rawLat.toRadixString(16)}), lon=$rawLon (0x${rawLon.toRadixString(16)}), alt=$rawAlt (0x${rawAlt.toRadixString(16)})',
|
' GPS Location (raw 24-bit BE): lat=$rawLat (0x${rawLat.toRadixString(16).padLeft(6, '0')}), lon=$rawLon (0x${rawLon.toRadixString(16).padLeft(6, '0')}), alt=$rawAlt (0x${rawAlt.toRadixString(16).padLeft(6, '0')})',
|
||||||
);
|
);
|
||||||
debugPrint(
|
debugPrint(
|
||||||
' GPS Location (decoded): ${lat.toStringAsFixed(6)}°, ${lon.toStringAsFixed(6)}°, altitude=${alt.toStringAsFixed(2)}m',
|
' GPS Location (decoded): ${lat.toStringAsFixed(6)}°, ${lon.toStringAsFixed(6)}°, altitude=${alt.toStringAsFixed(2)}m',
|
||||||
@@ -240,7 +254,10 @@ class CayenneLppParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create Cayenne LPP data for GPS location
|
/// Create Cayenne LPP data for GPS location
|
||||||
/// MeshCore GPS format: int32 LE (4 bytes each) × 10000 for lat/lon, × 100 for alt
|
/// Standard Cayenne LPP GPS format (type 0x88):
|
||||||
|
/// - Latitude: 3 bytes, signed 24-bit, big-endian, × 10000
|
||||||
|
/// - Longitude: 3 bytes, signed 24-bit, big-endian, × 10000
|
||||||
|
/// - Altitude: 3 bytes, signed 24-bit, big-endian, × 100
|
||||||
static Uint8List createGpsData({
|
static Uint8List createGpsData({
|
||||||
required double latitude,
|
required double latitude,
|
||||||
required double longitude,
|
required double longitude,
|
||||||
@@ -252,26 +269,27 @@ class CayenneLppParser {
|
|||||||
buffer.add(channel);
|
buffer.add(channel);
|
||||||
buffer.add(MeshCoreConstants.lppGps);
|
buffer.add(MeshCoreConstants.lppGps);
|
||||||
|
|
||||||
// Latitude (int32 LE, 4 bytes, signed, 0.0001° precision)
|
// Latitude (signed 24-bit BE, 3 bytes, 0.0001° precision)
|
||||||
final lat = (latitude * 10000).round();
|
int lat = (latitude * 10000).round();
|
||||||
buffer.add(lat & 0xFF); // Byte 0 (LSB)
|
// Handle negative values (two's complement for 24-bit)
|
||||||
|
if (lat < 0) lat = lat + 0x1000000;
|
||||||
|
buffer.add((lat >> 16) & 0xFF); // Byte 0 (MSB)
|
||||||
buffer.add((lat >> 8) & 0xFF); // Byte 1
|
buffer.add((lat >> 8) & 0xFF); // Byte 1
|
||||||
buffer.add((lat >> 16) & 0xFF); // Byte 2
|
buffer.add(lat & 0xFF); // Byte 2 (LSB)
|
||||||
buffer.add((lat >> 24) & 0xFF); // Byte 3 (MSB)
|
|
||||||
|
|
||||||
// Longitude (int32 LE, 4 bytes, signed, 0.0001° precision)
|
// Longitude (signed 24-bit BE, 3 bytes, 0.0001° precision)
|
||||||
final lon = (longitude * 10000).round();
|
int lon = (longitude * 10000).round();
|
||||||
buffer.add(lon & 0xFF); // Byte 0 (LSB)
|
if (lon < 0) lon = lon + 0x1000000;
|
||||||
|
buffer.add((lon >> 16) & 0xFF); // Byte 0 (MSB)
|
||||||
buffer.add((lon >> 8) & 0xFF); // Byte 1
|
buffer.add((lon >> 8) & 0xFF); // Byte 1
|
||||||
buffer.add((lon >> 16) & 0xFF); // Byte 2
|
buffer.add(lon & 0xFF); // Byte 2 (LSB)
|
||||||
buffer.add((lon >> 24) & 0xFF); // Byte 3 (MSB)
|
|
||||||
|
|
||||||
// Altitude (int32 LE, 4 bytes, signed, 0.01m precision)
|
// Altitude (signed 24-bit BE, 3 bytes, 0.01m precision)
|
||||||
final alt = (altitude * 100).round();
|
int alt = (altitude * 100).round();
|
||||||
buffer.add(alt & 0xFF); // Byte 0 (LSB)
|
if (alt < 0) alt = alt + 0x1000000;
|
||||||
|
buffer.add((alt >> 16) & 0xFF); // Byte 0 (MSB)
|
||||||
buffer.add((alt >> 8) & 0xFF); // Byte 1
|
buffer.add((alt >> 8) & 0xFF); // Byte 1
|
||||||
buffer.add((alt >> 16) & 0xFF); // Byte 2
|
buffer.add(alt & 0xFF); // Byte 2 (LSB)
|
||||||
buffer.add((alt >> 24) & 0xFF); // Byte 3 (MSB)
|
|
||||||
|
|
||||||
return Uint8List.fromList(buffer);
|
return Uint8List.fromList(buffer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ class ContactTile extends StatelessWidget {
|
|||||||
this.onNavigateToMap,
|
this.onNavigateToMap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Get localized time since last seen
|
||||||
|
String _getLocalizedTimeSinceLastSeen(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
final diff = DateTime.now().difference(contact.lastSeenTime);
|
||||||
|
|
||||||
|
if (diff.inMinutes < 1) return l10n.justNow;
|
||||||
|
if (diff.inMinutes < 60) return l10n.minutesAgo(diff.inMinutes);
|
||||||
|
if (diff.inHours < 24) return l10n.hoursAgo(diff.inHours);
|
||||||
|
return l10n.daysAgo(diff.inDays);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appProvider = context.watch<AppProvider>();
|
final appProvider = context.watch<AppProvider>();
|
||||||
@@ -194,7 +205,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'${AppLocalizations.of(context)!.lastSeen}: ${contact.timeSinceLastSeen}',
|
'${AppLocalizations.of(context)!.lastSeen}: ${_getLocalizedTimeSinceLastSeen(context)}',
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -337,7 +348,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
contact.timeSinceLastSeen,
|
_getLocalizedTimeSinceLastSeen(context),
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
if (location != null) ...[
|
if (location != null) ...[
|
||||||
@@ -662,7 +673,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
_DetailRow(
|
_DetailRow(
|
||||||
AppLocalizations.of(context)!.lastSeen,
|
AppLocalizations.of(context)!.lastSeen,
|
||||||
contact.timeSinceLastSeen,
|
_getLocalizedTimeSinceLastSeen(context),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Room Login Status
|
// Room Login Status
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:meshcore_sar_app/services/meshcore_constants.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('CayenneLppParser - GPS Codec Tests', () {
|
group('CayenneLppParser - GPS Codec Tests', () {
|
||||||
test('GPS encoding uses correct 4-byte int32 LE format', () {
|
test('GPS encoding uses correct 3-byte signed BE format', () {
|
||||||
// Test coordinates (Ljubljana, Slovenia)
|
// Test coordinates (Ljubljana, Slovenia)
|
||||||
const double lat = 46.0569;
|
const double lat = 46.0569;
|
||||||
const double lon = 14.5058;
|
const double lon = 14.5058;
|
||||||
@@ -20,37 +20,52 @@ void main() {
|
|||||||
channel: 0,
|
channel: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Expected format:
|
// Expected format (Standard Cayenne LPP):
|
||||||
// [0] = channel (0)
|
// [0] = channel (0)
|
||||||
// [1] = type (136 = 0x88 = lppGps)
|
// [1] = type (136 = 0x88 = lppGps)
|
||||||
// [2-5] = lat as int32 LE
|
// [2-4] = lat as 24-bit signed BE
|
||||||
// [6-9] = lon as int32 LE
|
// [5-7] = lon as 24-bit signed BE
|
||||||
// [10-13] = alt as int32 LE
|
// [8-10] = alt as 24-bit signed BE
|
||||||
expect(encoded.length, equals(14));
|
expect(encoded.length, equals(11));
|
||||||
expect(encoded[0], equals(0)); // channel
|
expect(encoded[0], equals(0)); // channel
|
||||||
expect(encoded[1], equals(MeshCoreConstants.lppGps)); // type 0x88
|
expect(encoded[1], equals(MeshCoreConstants.lppGps)); // type 0x88
|
||||||
|
|
||||||
// Verify little-endian encoding
|
// Verify big-endian encoding (3 bytes each)
|
||||||
final buffer = ByteData.sublistView(encoded);
|
final latEncoded =
|
||||||
final latEncoded = buffer.getInt32(2, Endian.little);
|
(encoded[2] << 16) | (encoded[3] << 8) | encoded[4];
|
||||||
final lonEncoded = buffer.getInt32(6, Endian.little);
|
final lonEncoded =
|
||||||
final altEncoded = buffer.getInt32(10, Endian.little);
|
(encoded[5] << 16) | (encoded[6] << 8) | encoded[7];
|
||||||
|
final altEncoded =
|
||||||
|
(encoded[8] << 16) | (encoded[9] << 8) | encoded[10];
|
||||||
|
|
||||||
expect(latEncoded, equals(460569)); // 46.0569 * 10000
|
expect(latEncoded, equals(460569)); // 46.0569 * 10000
|
||||||
expect(lonEncoded, equals(145058)); // 14.5058 * 10000
|
expect(lonEncoded, equals(145058)); // 14.5058 * 10000
|
||||||
expect(altEncoded, equals(29500)); // 295.0 * 100
|
expect(altEncoded, equals(29500)); // 295.0 * 100
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GPS decoding uses correct divisor (10000, not 1000000)', () {
|
test('GPS decoding uses correct 3-byte BE format and divisor', () {
|
||||||
// Create raw GPS telemetry packet
|
// Create raw GPS telemetry packet (standard Cayenne LPP format)
|
||||||
final buffer = ByteData(14);
|
final buffer = <int>[];
|
||||||
buffer.setUint8(0, 0); // channel
|
buffer.add(0); // channel
|
||||||
buffer.setUint8(1, MeshCoreConstants.lppGps); // type
|
buffer.add(MeshCoreConstants.lppGps); // type 0x88
|
||||||
buffer.setInt32(2, 460569, Endian.little); // lat: 46.0569 * 10000
|
|
||||||
buffer.setInt32(6, 145058, Endian.little); // lon: 14.5058 * 10000
|
|
||||||
buffer.setInt32(10, 29500, Endian.little); // alt: 295.0 * 100
|
|
||||||
|
|
||||||
final telemetry = CayenneLppParser.parse(buffer.buffer.asUint8List());
|
// Lat: 46.0569 * 10000 = 460569 = 0x070719
|
||||||
|
buffer.add(0x07); // MSB
|
||||||
|
buffer.add(0x07);
|
||||||
|
buffer.add(0x19); // LSB
|
||||||
|
|
||||||
|
// Lon: 14.5058 * 10000 = 145058 = 0x0236A2
|
||||||
|
buffer.add(0x02);
|
||||||
|
buffer.add(0x36);
|
||||||
|
buffer.add(0xA2);
|
||||||
|
|
||||||
|
// Alt: 295.0 * 100 = 29500 = 0x7 33C
|
||||||
|
buffer.add(0x00);
|
||||||
|
buffer.add(0x73);
|
||||||
|
buffer.add(0x3C);
|
||||||
|
|
||||||
|
final telemetry =
|
||||||
|
CayenneLppParser.parse(Uint8List.fromList(buffer));
|
||||||
|
|
||||||
expect(telemetry.gpsLocation, isNotNull);
|
expect(telemetry.gpsLocation, isNotNull);
|
||||||
expect(telemetry.gpsLocation!.latitude, closeTo(46.0569, 0.0001));
|
expect(telemetry.gpsLocation!.latitude, closeTo(46.0569, 0.0001));
|
||||||
@@ -104,20 +119,22 @@ void main() {
|
|||||||
// and any validation warnings are logged (not enforced)
|
// and any validation warnings are logged (not enforced)
|
||||||
|
|
||||||
// Valid coordinates should decode without issue
|
// Valid coordinates should decode without issue
|
||||||
final validBuffer = ByteData(14);
|
final validBuffer = <int>[];
|
||||||
validBuffer.setUint8(0, 0);
|
validBuffer.add(0); // channel
|
||||||
validBuffer.setUint8(1, MeshCoreConstants.lppGps);
|
validBuffer.add(MeshCoreConstants.lppGps);
|
||||||
validBuffer.setInt32(2, 450000, Endian.little); // 45.0°
|
|
||||||
validBuffer.setInt32(6, 100000, Endian.little); // 10.0°
|
|
||||||
validBuffer.setInt32(10, 0, Endian.little);
|
|
||||||
|
|
||||||
final telemetry = CayenneLppParser.parse(
|
// Lat: 45.0 * 10000 = 450000 = 0x06DDD0 (3 bytes BE)
|
||||||
validBuffer.buffer.asUint8List(),
|
validBuffer.addAll([0x06, 0xDD, 0xD0]);
|
||||||
);
|
// Lon: 10.0 * 10000 = 100000 = 0x0186A0 (3 bytes BE)
|
||||||
|
validBuffer.addAll([0x01, 0x86, 0xA0]);
|
||||||
|
// Alt: 0 (3 bytes BE)
|
||||||
|
validBuffer.addAll([0x00, 0x00, 0x00]);
|
||||||
|
|
||||||
|
final telemetry = CayenneLppParser.parse(Uint8List.fromList(validBuffer));
|
||||||
|
|
||||||
expect(telemetry.gpsLocation, isNotNull);
|
expect(telemetry.gpsLocation, isNotNull);
|
||||||
expect(telemetry.gpsLocation!.latitude, equals(45.0));
|
expect(telemetry.gpsLocation!.latitude, closeTo(45.0, 0.0001));
|
||||||
expect(telemetry.gpsLocation!.longitude, equals(10.0));
|
expect(telemetry.gpsLocation!.longitude, closeTo(10.0, 0.0001));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GPS encoding handles negative coordinates correctly', () {
|
test('GPS encoding handles negative coordinates correctly', () {
|
||||||
@@ -129,13 +146,18 @@ void main() {
|
|||||||
longitude: lon,
|
longitude: lon,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify signed int32 encoding
|
// Verify 3-byte signed encoding
|
||||||
final buffer = ByteData.sublistView(encoded);
|
// Lat: -33.8688 * 10000 = -338688
|
||||||
final latEncoded = buffer.getInt32(2, Endian.little);
|
// In 24-bit two's complement: -338688 + 0x1000000 = 16438608 = 0xFAD4E0
|
||||||
final lonEncoded = buffer.getInt32(6, Endian.little);
|
final latEncoded =
|
||||||
|
(encoded[2] << 16) | (encoded[3] << 8) | encoded[4];
|
||||||
|
// Lon: -151.2093 * 10000 = -1512093
|
||||||
|
// In 24-bit two's complement: -1512093 + 0x1000000 = 15265123 = 0xE8E963
|
||||||
|
final lonEncoded =
|
||||||
|
(encoded[5] << 16) | (encoded[6] << 8) | encoded[7];
|
||||||
|
|
||||||
expect(latEncoded, equals(-338688)); // -33.8688 * 10000
|
expect(latEncoded, equals(0xFAD4E0)); // Verify two's complement
|
||||||
expect(lonEncoded, equals(-1512093)); // -151.2093 * 10000
|
expect(lonEncoded, equals(0xE8E963));
|
||||||
|
|
||||||
// Verify decoding
|
// Verify decoding
|
||||||
final decoded = CayenneLppParser.parse(encoded);
|
final decoded = CayenneLppParser.parse(encoded);
|
||||||
@@ -150,8 +172,10 @@ void main() {
|
|||||||
altitude: 1234.56,
|
altitude: 1234.56,
|
||||||
);
|
);
|
||||||
|
|
||||||
final buffer = ByteData.sublistView(encoded);
|
// Altitude is at bytes 8-10 (3 bytes BE)
|
||||||
final altEncoded = buffer.getInt32(10, Endian.little);
|
// Alt: 1234.56 * 100 = 123456 = 0x01E240
|
||||||
|
final altEncoded =
|
||||||
|
(encoded[8] << 16) | (encoded[9] << 8) | encoded[10];
|
||||||
|
|
||||||
// Altitude precision is 0.01m (divide by 100)
|
// Altitude precision is 0.01m (divide by 100)
|
||||||
expect(altEncoded, equals(123456)); // 1234.56 * 100
|
expect(altEncoded, equals(123456)); // 1234.56 * 100
|
||||||
@@ -177,33 +201,42 @@ void main() {
|
|||||||
expect(decoded.extraSensorData!['altitude_5'], isNotNull);
|
expect(decoded.extraSensorData!['altitude_5'], isNotNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('OLD BUG: divisor 1000000 would cause 99% error', () {
|
test('OLD BUG: Using 4-byte LE instead of 3-byte BE caused wrong coords',
|
||||||
|
() {
|
||||||
// This test documents the bug that was fixed
|
// This test documents the bug that was fixed
|
||||||
// The old code divided by 1,000,000 instead of 10,000
|
// The old code used 4-byte int32 LE (MeshCore advertisement format)
|
||||||
|
// instead of 3-byte signed BE (standard Cayenne LPP format)
|
||||||
|
|
||||||
final buffer = ByteData(14);
|
// Real data from device:
|
||||||
buffer.setUint8(0, 0);
|
// Hex: 06 f7 08 02 38 0e 01 2c 46
|
||||||
buffer.setUint8(1, MeshCoreConstants.lppGps);
|
final realData = <int>[
|
||||||
buffer.setInt32(2, 460569, Endian.little); // Should be 46.0569°
|
0x00, 0x88, // channel 0, type GPS
|
||||||
buffer.setInt32(6, 145058, Endian.little); // Should be 14.5058°
|
0x06, 0xf7, 0x08, // lat (3 bytes BE)
|
||||||
buffer.setInt32(10, 0, Endian.little);
|
0x02, 0x38, 0x0e, // lon (3 bytes BE)
|
||||||
|
0x01, 0x2c, 0x46, // alt (3 bytes BE)
|
||||||
|
];
|
||||||
|
|
||||||
// With CORRECT divisor (10000):
|
// CORRECT decoding (3-byte BE):
|
||||||
final correctLat = 460569 / 10000.0; // 46.0569
|
final correctLat =
|
||||||
final correctLon = 145058 / 10000.0; // 14.5058
|
((0x06 << 16) | (0xf7 << 8) | 0x08) / 10000.0; // 45.6456°
|
||||||
|
final correctLon =
|
||||||
|
((0x02 << 16) | (0x38 << 8) | 0x0e) / 10000.0; // 14.5422°
|
||||||
|
|
||||||
// With OLD BUGGY divisor (1000000):
|
// OLD BUGGY decoding (4-byte LE - reads wrong bytes!):
|
||||||
final buggyLat = 460569 / 1000000.0; // 0.460569 (100x too small!)
|
// Would read: lat=06f70802, lon=38010e2c (completely wrong)
|
||||||
final buggyLon = 145058 / 1000000.0; // 0.145058 (100x too small!)
|
final buggyLatRaw = 0x02 | (0x08 << 8) | (0xf7 << 16) | (0x06 << 24);
|
||||||
|
final buggyLat = buggyLatRaw / 10000.0; // 3414.1958° (out of range!)
|
||||||
|
|
||||||
// Verify the bug would have caused ~99% error
|
// Verify current implementation decodes correctly
|
||||||
final errorPercent = ((correctLat - buggyLat) / correctLat) * 100;
|
final telemetry = CayenneLppParser.parse(Uint8List.fromList(realData));
|
||||||
expect(errorPercent, closeTo(99.0, 0.1));
|
expect(telemetry.gpsLocation, isNotNull);
|
||||||
|
expect(telemetry.gpsLocation!.latitude, closeTo(correctLat, 0.0001));
|
||||||
|
expect(telemetry.gpsLocation!.longitude, closeTo(correctLon, 0.0001));
|
||||||
|
|
||||||
// Verify current implementation uses correct divisor
|
// Verify it doesn't produce the buggy values
|
||||||
final telemetry = CayenneLppParser.parse(buffer.buffer.asUint8List());
|
|
||||||
expect(telemetry.gpsLocation!.latitude, equals(correctLat));
|
|
||||||
expect(telemetry.gpsLocation!.latitude, isNot(equals(buggyLat)));
|
expect(telemetry.gpsLocation!.latitude, isNot(equals(buggyLat)));
|
||||||
|
expect(telemetry.gpsLocation!.latitude, lessThan(90.0)); // Valid range
|
||||||
|
expect(telemetry.gpsLocation!.latitude, greaterThan(-90.0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -353,32 +386,31 @@ void main() {
|
|||||||
|
|
||||||
test('multiple sensors in single packet', () {
|
test('multiple sensors in single packet', () {
|
||||||
// Create a combined packet with multiple sensors
|
// Create a combined packet with multiple sensors
|
||||||
final buffer = ByteData(22);
|
final buffer = <int>[];
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
// GPS (channel 2) - use channel 2 to avoid battery auto-detection
|
// GPS (channel 2) - use channel 2 to avoid battery auto-detection
|
||||||
buffer.setUint8(offset++, 2); // channel
|
buffer.add(2); // channel
|
||||||
buffer.setUint8(offset++, MeshCoreConstants.lppGps);
|
buffer.add(MeshCoreConstants.lppGps);
|
||||||
buffer.setInt32(offset, 460569, Endian.little); // lat
|
// Lat: 46.0569 * 10000 = 460569 = 0x070719 (3 bytes BE)
|
||||||
offset += 4;
|
buffer.addAll([0x07, 0x07, 0x19]);
|
||||||
buffer.setInt32(offset, 145058, Endian.little); // lon
|
// Lon: 14.5058 * 10000 = 145058 = 0x0236A2 (3 bytes BE)
|
||||||
offset += 4;
|
buffer.addAll([0x02, 0x36, 0xA2]);
|
||||||
buffer.setInt32(offset, 0, Endian.little); // alt
|
// Alt: 0 (3 bytes BE)
|
||||||
offset += 4;
|
buffer.addAll([0x00, 0x00, 0x00]);
|
||||||
|
|
||||||
// Temperature (channel 3)
|
// Temperature (channel 3)
|
||||||
buffer.setUint8(offset++, 3); // channel
|
buffer.add(3); // channel
|
||||||
buffer.setUint8(offset++, MeshCoreConstants.lppTemperatureSensor);
|
buffer.add(MeshCoreConstants.lppTemperatureSensor);
|
||||||
buffer.setInt16(offset, 235, Endian.big); // 23.5°C * 10
|
buffer.add(0x00); // 23.5°C * 10 = 235 (2 bytes BE)
|
||||||
offset += 2;
|
buffer.add(0xEB);
|
||||||
|
|
||||||
// Battery (channel 0 - required for battery detection)
|
// Battery (channel 0 - required for battery detection)
|
||||||
buffer.setUint8(offset++, 0); // channel
|
buffer.add(0); // channel
|
||||||
buffer.setUint8(offset++, MeshCoreConstants.lppAnalogInput);
|
buffer.add(MeshCoreConstants.lppAnalogInput);
|
||||||
buffer.setInt16(offset, 385, Endian.big); // 3.85V * 100
|
buffer.add(0x01); // 3.85V * 100 = 385 (2 bytes BE)
|
||||||
offset += 2;
|
buffer.add(0x81);
|
||||||
|
|
||||||
final decoded = CayenneLppParser.parse(buffer.buffer.asUint8List());
|
final decoded = CayenneLppParser.parse(Uint8List.fromList(buffer));
|
||||||
|
|
||||||
// All sensors should be decoded
|
// All sensors should be decoded
|
||||||
expect(decoded.gpsLocation, isNotNull);
|
expect(decoded.gpsLocation, isNotNull);
|
||||||
|
|||||||
Reference in New Issue
Block a user