mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-12 20:02:01 +10:00
Initial commit: MeshCore Open Flutter client
Open-source Flutter client for MeshCore LoRa mesh networking devices. Features: - BLE device scanning and connection - Nordic UART Service (NUS) integration - Material 3 design with system theme support - Provider-based state management - Placeholder screens for chat, contacts, and settings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
|
||||
class BleDebugLogEntry {
|
||||
final DateTime timestamp;
|
||||
final bool outgoing;
|
||||
final String description;
|
||||
final Uint8List payload;
|
||||
|
||||
BleDebugLogEntry({
|
||||
required this.timestamp,
|
||||
required this.outgoing,
|
||||
required this.description,
|
||||
required this.payload,
|
||||
});
|
||||
|
||||
String get hexPreview {
|
||||
const maxBytes = 64;
|
||||
final bytes = payload.length > maxBytes ? payload.sublist(0, maxBytes) : payload;
|
||||
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
|
||||
return payload.length > maxBytes ? '$hex …' : hex;
|
||||
}
|
||||
}
|
||||
|
||||
class BleRawLogRxEntry {
|
||||
final DateTime timestamp;
|
||||
final Uint8List payload;
|
||||
|
||||
BleRawLogRxEntry({
|
||||
required this.timestamp,
|
||||
required this.payload,
|
||||
});
|
||||
|
||||
String get hexPreview {
|
||||
const maxBytes = 64;
|
||||
final bytes = payload.length > maxBytes ? payload.sublist(0, maxBytes) : payload;
|
||||
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
|
||||
return payload.length > maxBytes ? '$hex …' : hex;
|
||||
}
|
||||
}
|
||||
|
||||
class BleDebugLogService extends ChangeNotifier {
|
||||
static const int maxEntries = 500;
|
||||
final List<BleDebugLogEntry> _entries = [];
|
||||
final List<BleRawLogRxEntry> _rawLogRxEntries = [];
|
||||
|
||||
List<BleDebugLogEntry> get entries => List.unmodifiable(_entries);
|
||||
List<BleRawLogRxEntry> get rawLogRxEntries => List.unmodifiable(_rawLogRxEntries);
|
||||
|
||||
void logFrame(Uint8List frame, {required bool outgoing, String? note}) {
|
||||
if (frame.isEmpty) return;
|
||||
final code = frame[0];
|
||||
final description = _describeFrame(code, frame, outgoing, note);
|
||||
_entries.add(
|
||||
BleDebugLogEntry(
|
||||
timestamp: DateTime.now(),
|
||||
outgoing: outgoing,
|
||||
description: description,
|
||||
payload: Uint8List.fromList(frame),
|
||||
),
|
||||
);
|
||||
|
||||
if (_entries.length > maxEntries) {
|
||||
_entries.removeRange(0, _entries.length - maxEntries);
|
||||
}
|
||||
|
||||
if (!outgoing && code == pushCodeLogRxData && frame.length > 3) {
|
||||
_rawLogRxEntries.add(
|
||||
BleRawLogRxEntry(
|
||||
timestamp: DateTime.now(),
|
||||
payload: Uint8List.fromList(frame.sublist(3)),
|
||||
),
|
||||
);
|
||||
if (_rawLogRxEntries.length > maxEntries) {
|
||||
_rawLogRxEntries.removeRange(0, _rawLogRxEntries.length - maxEntries);
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_entries.clear();
|
||||
_rawLogRxEntries.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String _describeFrame(int code, Uint8List frame, bool outgoing, String? note) {
|
||||
final label = _codeLabel(code);
|
||||
final prefix = outgoing ? 'TX' : 'RX';
|
||||
final extra = _frameDetail(code, frame);
|
||||
final noteText = note != null ? ' • $note' : '';
|
||||
return '$prefix $label$extra$noteText';
|
||||
}
|
||||
|
||||
String _codeLabel(int code) {
|
||||
switch (code) {
|
||||
case cmdAppStart:
|
||||
return 'CMD_APP_START';
|
||||
case cmdSendTxtMsg:
|
||||
return 'CMD_SEND_TXT_MSG';
|
||||
case cmdSendChannelTxtMsg:
|
||||
return 'CMD_SEND_CHANNEL_TXT_MSG';
|
||||
case cmdGetContacts:
|
||||
return 'CMD_GET_CONTACTS';
|
||||
case cmdGetDeviceTime:
|
||||
return 'CMD_GET_DEVICE_TIME';
|
||||
case cmdSetDeviceTime:
|
||||
return 'CMD_SET_DEVICE_TIME';
|
||||
case cmdSendSelfAdvert:
|
||||
return 'CMD_SEND_SELF_ADVERT';
|
||||
case cmdSetAdvertName:
|
||||
return 'CMD_SET_ADVERT_NAME';
|
||||
case cmdAddUpdateContact:
|
||||
return 'CMD_ADD_UPDATE_CONTACT';
|
||||
case cmdSyncNextMessage:
|
||||
return 'CMD_SYNC_NEXT_MESSAGE';
|
||||
case cmdSetRadioParams:
|
||||
return 'CMD_SET_RADIO_PARAMS';
|
||||
case cmdSetRadioTxPower:
|
||||
return 'CMD_SET_RADIO_TX_POWER';
|
||||
case cmdResetPath:
|
||||
return 'CMD_RESET_PATH';
|
||||
case cmdRemoveContact:
|
||||
return 'CMD_REMOVE_CONTACT';
|
||||
case cmdReboot:
|
||||
return 'CMD_REBOOT';
|
||||
case cmdGetBattAndStorage:
|
||||
return 'CMD_GET_BATT_AND_STORAGE';
|
||||
case cmdSendLogin:
|
||||
return 'CMD_SEND_LOGIN';
|
||||
case cmdGetChannel:
|
||||
return 'CMD_GET_CHANNEL';
|
||||
case cmdSetChannel:
|
||||
return 'CMD_SET_CHANNEL';
|
||||
case cmdGetRadioSettings:
|
||||
return 'CMD_GET_RADIO_SETTINGS';
|
||||
case respCodeOk:
|
||||
return 'RESP_CODE_OK';
|
||||
case respCodeErr:
|
||||
return 'RESP_CODE_ERR';
|
||||
case respCodeContactsStart:
|
||||
return 'RESP_CODE_CONTACTS_START';
|
||||
case respCodeContact:
|
||||
return 'RESP_CODE_CONTACT';
|
||||
case respCodeEndOfContacts:
|
||||
return 'RESP_CODE_END_OF_CONTACTS';
|
||||
case respCodeSelfInfo:
|
||||
return 'RESP_CODE_SELF_INFO';
|
||||
case respCodeSent:
|
||||
return 'RESP_CODE_SENT';
|
||||
case respCodeContactMsgRecv:
|
||||
return 'RESP_CODE_CONTACT_MSG_RECV';
|
||||
case respCodeChannelMsgRecv:
|
||||
return 'RESP_CODE_CHANNEL_MSG_RECV';
|
||||
case respCodeCurrTime:
|
||||
return 'RESP_CODE_CURR_TIME';
|
||||
case respCodeNoMoreMessages:
|
||||
return 'RESP_CODE_NO_MORE_MESSAGES';
|
||||
case respCodeBattAndStorage:
|
||||
return 'RESP_CODE_BATT_AND_STORAGE';
|
||||
case respCodeContactMsgRecvV3:
|
||||
return 'RESP_CODE_CONTACT_MSG_RECV_V3';
|
||||
case respCodeChannelMsgRecvV3:
|
||||
return 'RESP_CODE_CHANNEL_MSG_RECV_V3';
|
||||
case respCodeChannelInfo:
|
||||
return 'RESP_CODE_CHANNEL_INFO';
|
||||
case respCodeRadioSettings:
|
||||
return 'RESP_CODE_RADIO_SETTINGS';
|
||||
case pushCodeAdvert:
|
||||
return 'PUSH_CODE_ADVERT';
|
||||
case pushCodePathUpdated:
|
||||
return 'PUSH_CODE_PATH_UPDATED';
|
||||
case pushCodeSendConfirmed:
|
||||
return 'PUSH_CODE_SEND_CONFIRMED';
|
||||
case pushCodeMsgWaiting:
|
||||
return 'PUSH_CODE_MSG_WAITING';
|
||||
case pushCodeLoginSuccess:
|
||||
return 'PUSH_CODE_LOGIN_SUCCESS';
|
||||
case pushCodeLoginFail:
|
||||
return 'PUSH_CODE_LOGIN_FAIL';
|
||||
case pushCodeLogRxData:
|
||||
return 'PUSH_CODE_LOG_RX_DATA';
|
||||
case pushCodeNewAdvert:
|
||||
return 'PUSH_CODE_NEW_ADVERT';
|
||||
default:
|
||||
return 'CODE_$code';
|
||||
}
|
||||
}
|
||||
|
||||
String _frameDetail(int code, Uint8List frame) {
|
||||
switch (code) {
|
||||
case respCodeSent:
|
||||
if (frame.length >= 10) {
|
||||
final timeoutMs = readUint32LE(frame, 6);
|
||||
return ' • timeout=${timeoutMs}ms';
|
||||
}
|
||||
return '';
|
||||
case pushCodeSendConfirmed:
|
||||
if (frame.length >= 9) {
|
||||
final tripMs = readUint32LE(frame, 5);
|
||||
return ' • trip=${tripMs}ms';
|
||||
}
|
||||
return '';
|
||||
case pushCodeLoginSuccess:
|
||||
return ' • login ok';
|
||||
case pushCodeLoginFail:
|
||||
return ' • login fail';
|
||||
case respCodeBattAndStorage:
|
||||
if (frame.length >= 3) {
|
||||
final mv = readUint16LE(frame, 1);
|
||||
return ' • ${mv}mV';
|
||||
}
|
||||
return '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user