diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index a4faf0e..f987a62 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:ffi'; import 'dart:typed_data'; // Buffer Reader - sequential binary data reader with pointer tracking @@ -18,6 +19,10 @@ class BufferReader { return data; } + void skipBytes(int count) { + _pointer += count; + } + Uint8List readRemainingBytes() => readBytes(remaining); String readString() => diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 02faff5..01e777a 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -1,6 +1,9 @@ import 'dart:async'; +import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:meshcore_open/widgets/path_trace_dialog.dart'; import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; @@ -51,11 +54,14 @@ class _ContactsScreenState extends State final ContactGroupStore _groupStore = ContactGroupStore(); List _groups = []; Timer? _searchDebounce; - + StreamSubscription? _frameSubscription; + Uint8List _tagData = Uint8List(4); + @override void initState() { super.initState(); _loadGroups(); + _setupFrameListener(); } @override @@ -65,6 +71,44 @@ class _ContactsScreenState extends State super.dispose(); } + void _setupFrameListener() { + final connector = Provider.of(context, listen: false); + + // Listen for incoming text messages from the repeater + _frameSubscription = connector.receivedFrames.listen((frame) { + if (frame.isEmpty) return; + + if (frame[0] == respCodeSent) { + _tagData = frame.sublist(2, 6); + print("Stored tag data: $_tagData"); + } + + // Check if it's a binary response + if (frame[0] == pushCodeTraceData && listEquals(frame.sublist(4, 8), _tagData)) { + if (!mounted) return; + _handleTraceResponse(frame); + } + }); + } + + Future _handleTraceResponse(Uint8List frame)async { + final buffer = BufferReader(frame); + buffer.skipBytes(2); // Skip push code and reserved byte + int pathLength = buffer.readUInt8(); + buffer.skipBytes(5); // Skip Flag byte and tag data + buffer.skipBytes(4); // Skip auth code + Uint8List pathData = buffer.readBytes(pathLength); + Uint8List snrData = buffer.readRemainingBytes(); + print("Received path data length: $pathLength, SNR data length: ${snrData.length}"); + showDialog( + context: context, + builder: (context) => PathTraceDialog( + pathData: pathData, + snrData: snrData, + ), + ); + } + Future _loadGroups() async { final groups = await _groupStore.loadGroups(); if (!mounted) return; @@ -757,11 +801,12 @@ class _ContactsScreenState extends State leading: const Icon(Icons.radar, color: Colors.green), title: Text("Ping"), onTap: () async { + Navigator.pop(sheetContext); final frame = buildTraceReq( DateTime.now().millisecondsSinceEpoch ~/ 1000, 0, 0, - payload: contact.publicKey.sublist(0,1), + payload: Uint8List.fromList([0x85,0x91,0x07,0x91,0x85]) //contact.publicKey.sublist(0,1), ); await connector.sendFrame(frame); } diff --git a/lib/widgets/path_trace_dialog.dart b/lib/widgets/path_trace_dialog.dart new file mode 100644 index 0000000..c5e4cad --- /dev/null +++ b/lib/widgets/path_trace_dialog.dart @@ -0,0 +1,52 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:meshcore_open/widgets/snr_indicator.dart'; + +class PathTraceDialog extends StatefulWidget { + + const PathTraceDialog({ + super.key, + required this.pathData, + required this.snrData, + }); + + final Uint8List pathData; + final Uint8List snrData; + + @override + State createState() => _PathTraceDialogState(); +} + +class _PathTraceDialogState extends State { + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Path Trace'), + content: SizedBox( + width: double.maxFinite, + child: ListView.builder( + itemCount: widget.snrData.length, + itemBuilder: (context, index) { + return ListTile( + leading: index >= widget.snrData.length / 2 ? Icon(Icons.arrow_circle_left) : Icon(Icons.arrow_circle_right), + title: index == 0 || index == widget.snrData.length - 1 ? ( index == 0 ? Text('You to 0x${widget.pathData[0].toRadixString(16).toUpperCase()}') : Text('0x${widget.pathData[widget.pathData.length - 1].toRadixString(16).toUpperCase()} to You')) : Text('0x${widget.pathData[index-1].toRadixString(16).toUpperCase()} to 0x${widget.pathData[index].toRadixString(16).toUpperCase()}'), + trailing: SNRIcon(snr: widget.snrData[index] / 4.0), + onTap: () { + // Handle item tap + }, + + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Close'), + ), + ], + ); + } +}