flutter_elink_ble 0.2.4 copy "flutter_elink_ble: ^0.2.4" to clipboard
flutter_elink_ble: ^0.2.4 copied to clipboard

ElinkThings BLE SDK plugin for scanning, connecting, and processing Elink BLE data.

example/lib/main.dart

import 'dart:async';
import 'dart:io' show Platform;

import 'package:flutter/material.dart';
import 'package:flutter_elink_ble/flutter_elink_ble.dart';

import 'bluetooth_connection_page.dart';
import 'connected_device_info.dart';
import 'example_time_utils.dart';
import 'scan_page.dart';
import 'wifi_provisioning_page.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const ElinkExampleApp());
}

class ElinkExampleApp extends StatelessWidget {
  const ElinkExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const ElinkHomePage(),
    );
  }
}

class ElinkHomePage extends StatefulWidget {
  const ElinkHomePage({super.key});

  @override
  State<ElinkHomePage> createState() => _ElinkHomePageState();
}

class _ElinkHomePageState extends State<ElinkHomePage> {
  static const int _maxLogCount = 160;

  /// Android 示例默认请求的 GATT MTU。
  static const int _defaultAndroidMtu = 517;

  final List<StreamSubscription<Object?>> _subscriptions = [];
  final Map<String, List<String>> _deviceLogs = <String, List<String>>{};
  final Set<String> _handshakeReadyRemoteIds = <String>{};
  List<ElinkScanResult> _scanResults = const <ElinkScanResult>[];
  ElinkAdapterState _adapterState = ElinkBle.adapterStateNow;
  bool _isScanning = false;
  bool _enableTlvParse = false;
  int _androidCommandResendCount = 0;
  final Set<String> _connectedRemoteIds = <String>{};
  final Map<String, String> _connectedMacAddresses = <String, String>{};
  final Map<String, String> _bmVersions = <String, String>{};
  String? _selectedRemoteId;

  @override
  void initState() {
    super.initState();
    _subscriptions
      ..add(
        ElinkBle.adapterState.listen((state) {
          setState(() => _adapterState = state);
        }),
      )
      ..add(
        ElinkBle.isScanning.listen((isScanning) {
          if (_isScanning == isScanning) {
            return;
          }
          setState(() => _isScanning = isScanning);
        }),
      )
      ..add(
        ElinkBle.scanResults.listen((results) {
          setState(() => _scanResults = results);
        }),
      )
      ..add(
        ElinkBle.connectionEvents.listen((event) {
          setState(() {
            switch (event.connectionState) {
              case ElinkConnectionState.connected:
                _connectedRemoteIds.add(event.remoteId);
                _selectedRemoteId = event.remoteId;
                _connectedMacAddresses[event.remoteId] = _macAddressFor(
                  event.remoteId,
                );
                break;
              case ElinkConnectionState.disconnected:
                _connectedRemoteIds.remove(event.remoteId);
                _connectedMacAddresses.remove(event.remoteId);
                _bmVersions.remove(event.remoteId);
                _handshakeReadyRemoteIds.remove(event.remoteId);
                if (_selectedRemoteId == event.remoteId) {
                  _selectedRemoteId = _connectedRemoteIds.isEmpty
                      ? null
                      : _connectedRemoteIds.first;
                }
                break;
              case ElinkConnectionState.connecting:
              case ElinkConnectionState.disconnecting:
                _selectedRemoteId ??= event.remoteId;
                break;
            }
          });
          _addDeviceLog(
            event.remoteId,
            '[connectionEvents] ${event.remoteId}: '
            '${event.connectionState.name}',
          );
        }),
      )
      ..add(
        ElinkBle.serviceDiscoveryEvents.listen((event) {
          _addDeviceLog(
            event.remoteId,
            '[serviceDiscoveryEvents] ${event.remoteId}: '
            '${event.serviceUuid} '
            '${event.characteristicUuids.map((uuid) => uuid.value).join(",")}',
          );
        }),
      )
      ..add(
        ElinkBle.protocolDataPackets.listen((packet) {
          _addDeviceLog(
            packet.remoteId,
            '[protocolDataPackets] ${packet.remoteId}: '
            '${packet.protocol.name.toUpperCase()} '
            'payload=${ElinkDataProcessor.formatHex(packet.data)}',
          );
          _addProtocolPacketParseLogs(packet);
        }),
      )
      ..add(
        ElinkBle.passthroughDataPackets.listen((packet) {
          _addDeviceLog(
            packet.remoteId,
            '[passthroughDataPackets] ${packet.remoteId}: '
            '${ElinkDataProcessor.formatHex(packet.data)}',
          );
          _addRawFrameParseLogs(
            source: 'passthroughDataPackets',
            remoteId: packet.remoteId,
            data: packet.data,
          );
        }),
      )
      ..add(
        ElinkBle.characteristicEvents.listen((event) {
          _addDeviceLog(
            event.remoteId,
            '[characteristicEvents] ${event.remoteId}: '
            '${event.operation.name} ${event.characteristicUuid} '
            '${ElinkDataProcessor.formatHex(event.data)}',
          );
          unawaited(_handleA7CharacteristicChanged(event));
          _addRawFrameParseLogs(
            source: 'characteristicEvents',
            remoteId: event.remoteId,
            data: event.data,
          );
        }),
      )
      ..add(
        ElinkBle.rssiEvents.listen((event) {
          _addDeviceLog(
            event.remoteId,
            '[rssiEvents] ${event.remoteId}: ${event.rssi}',
          );
        }),
      )
      ..add(
        ElinkBle.mtuEvents.listen((event) {
          _addDeviceLog(
            event.remoteId,
            '[mtuEvents] ${event.remoteId}: '
            'mtu=${event.mtu} available=${event.availableMtu}',
          );
        }),
      )
      ..add(
        ElinkBle.handshakeEvents.listen((event) {
          if (event.success) {
            _handshakeReadyRemoteIds.add(event.remoteId);
          }
          _addDeviceLog(
            event.remoteId,
            '[handshakeEvents] ${event.remoteId}: ${event.success}',
          );
        }),
      )
      ..add(
        ElinkBle.bmVersionEvents.listen((event) {
          if (_connectedRemoteIds.contains(event.remoteId)) {
            setState(() => _bmVersions[event.remoteId] = event.version);
          }
          _addDeviceLog(
            event.remoteId,
            '[bmVersionEvents] ${event.remoteId}: '
            'command=0x${event.command.toRadixString(16).padLeft(2, '0').toUpperCase()} '
            'type=${event.versionKind} '
            'version=${event.version}',
          );
        }),
      )
      ..add(
        ElinkBle.errors.listen((error) {
          final remoteId = _remoteIdFromError(error);
          if (remoteId != null) {
            _addDeviceLog(remoteId, '[errors] $error');
          }
        }),
      );
    unawaited(ElinkBle.refreshAdapterState());
  }

  @override
  void dispose() {
    for (final subscription in _subscriptions) {
      subscription.cancel();
    }
    unawaited(ElinkBle.dispose());
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Elink BLE'),
        actions: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Center(child: Text(_adapterState.name)),
          ),
        ],
      ),
      body: _buildCurrentPage(),
    );
  }

  /// Build the current example page from scan and connection state (根据扫描和连接状态构建当前示例页面).
  Widget _buildCurrentPage() {
    final connectedDevices = _connectedDevices();
    return DefaultTabController(
      key: ValueKey<String>(
        'tabs:${connectedDevices.length}:${_selectedRemoteId ?? ""}',
      ),
      length: connectedDevices.length + 1,
      initialIndex: _initialTabIndex(connectedDevices),
      child: Column(
        children: [
          TabBar(
            isScrollable: true,
            tabs: [
              const Tab(icon: Icon(Icons.bluetooth_searching), text: 'Scan'),
              ...connectedDevices.map(_buildDeviceTab),
            ],
          ),
          Expanded(
            child: TabBarView(
              children: [
                ScanPage(
                  adapterState: _adapterState,
                  isScanning: _isScanning,
                  scanResults: _scanResults,
                  connectedRemoteIds: _connectedRemoteIds,
                  onOpenBluetooth: () => unawaited(_openBluetooth()),
                  onStartScan: () => unawaited(_startScan()),
                  onStopScan: () => unawaited(_stopScan()),
                  onConnect: (device) => unawaited(_connect(device)),
                  showAndroidCommandResendSetting: Platform.isAndroid,
                  androidCommandResendCount: _androidCommandResendCount,
                  onAndroidCommandResendCountChanged: (resendCount) {
                    unawaited(_setAndroidCommandResendCount(resendCount));
                  },
                ),
                ...connectedDevices.map(_buildDevicePage),
              ],
            ),
          ),
        ],
      ),
    );
  }

  /// 计算动态 tab 初始索引,连接成功后自动切到对应设备。
  int _initialTabIndex(List<ConnectedDeviceInfo> connectedDevices) {
    final selectedRemoteId = _selectedRemoteId;
    if (selectedRemoteId == null) {
      return 0;
    }
    final deviceIndex = connectedDevices.indexWhere(
      (device) => device.remoteId == selectedRemoteId,
    );
    return deviceIndex < 0 ? 0 : deviceIndex + 1;
  }

  /// 构建单个已连接设备的 tab。
  Tab _buildDeviceTab(ConnectedDeviceInfo device) {
    return Tab(
      icon: const Icon(Icons.bluetooth_connected),
      text: _deviceTabLabel(device),
    );
  }

  /// 构建单个已连接设备的操作页。
  Widget _buildDevicePage(ConnectedDeviceInfo device) {
    final remoteId = device.remoteId;
    return BluetoothConnectionPage(
      connectedDevice: device,
      enableTlvParse: _enableTlvParse,
      logs: _logsForDevice(remoteId),
      onClearLogs: () => _clearDeviceLogs(remoteId),
      onDisconnect: () => unawaited(_disconnectDevice(remoteId)),
      onGetBmVersion: () => unawaited(_getBmVersion(remoteId)),
      mtuActionLabel: Platform.isIOS
          ? 'Get iOS MTU'
          : Platform.isAndroid
          ? 'Set MTU 517'
          : 'MTU',
      onMtuAction: () => unawaited(_handleMtu(remoteId)),
      onOpenWifiProvisioning: () => _openWifiProvisioning(remoteId),
      onEnableTlvParseChanged: (value) {
        setState(() => _enableTlvParse = value);
        _addDeviceLog(remoteId, '[parseConfig] tlv=$value');
      },
      showAndroidCommandResendSetting: Platform.isAndroid,
      androidCommandResendCount: _androidCommandResendCount,
      onAndroidCommandResendCountChanged: (resendCount) {
        unawaited(
          _setAndroidCommandResendCount(resendCount, logRemoteId: remoteId),
        );
      },
    );
  }

  /// 格式化设备 tab 文案。
  String _deviceTabLabel(ConnectedDeviceInfo device) {
    final id = device.remoteId;
    final shortId = id.length <= 8 ? id : id.substring(id.length - 8);
    return device.handshakeReady ? 'Ready $shortId' : shortId;
  }

  /// Build connected device view models for the device tab (构建设备页使用的已连接设备信息).
  List<ConnectedDeviceInfo> _connectedDevices() {
    return _connectedRemoteIds
        .map((remoteId) {
          final cachedMacAddress = _connectedMacAddresses[remoteId];
          return ConnectedDeviceInfo(
            remoteId: remoteId,
            macAddress: cachedMacAddress == null || cachedMacAddress.isEmpty
                ? _macAddressFor(remoteId)
                : cachedMacAddress,
            bmVersion: _bmVersions[remoteId],
            handshakeReady: _handshakeReadyRemoteIds.contains(remoteId),
          );
        })
        .toList(growable: false);
  }

  /// 获取指定设备 tab 当前展示的日志列表。
  List<String> _logsForDevice(String remoteId) {
    return _deviceLogs[remoteId] ?? const <String>[];
  }

  /// 清空指定设备 tab 的日志,不影响其它设备页面。
  void _clearDeviceLogs(String remoteId) {
    setState(() => _deviceLogs.remove(remoteId));
  }

  /// Resolve one device MAC from scan cache or connected cache (从扫描缓存或连接缓存解析设备 MAC).
  String _macAddressFor(String remoteId) {
    for (final result in _scanResults) {
      if (result.device.remoteId != remoteId) {
        continue;
      }
      if (result.device.macAddress.isNotEmpty) {
        return result.device.macAddress;
      }
      final macAddress = result.advertisementData.identity.macAddress;
      if (macAddress.isNotEmpty) {
        return macAddress;
      }
    }
    return _connectedMacAddresses[remoteId] ?? '';
  }

  /// Connect one BLE device after stopping this example scan, and log failures (停止示例页扫描后连接一个 BLE 设备,并记录失败).
  Future<void> _connect(ElinkDevice device) async {
    if (_connectedRemoteIds.contains(device.remoteId)) {
      setState(() => _selectedRemoteId = device.remoteId);
      return;
    }
    try {
      setState(() {
        _selectedRemoteId ??= device.remoteId;
        _connectedMacAddresses[device.remoteId] = device.macAddress;
      });
      if (_isScanning) {
        await ElinkBle.stopScan();
      }
      await ElinkBle.connect(device);
    } catch (error) {
      _addDeviceLog(device.remoteId, '[connect] ${device.remoteId}: $error');
    }
  }

  /// 根据当前平台处理 MTU:Android 请求 517,iOS 查询最大写入长度。
  Future<void> _handleMtu(
    String remoteId, {
    int androidMtu = _defaultAndroidMtu,
  }) async {
    try {
      if (Platform.isIOS) {
        final mtu = await ElinkBle.getIosMtu(remoteId);
        _addDeviceLog(
          remoteId,
          '[getIosMtu] $remoteId: '
          'withoutResponse=${mtu.maxWriteWithoutResponse} '
          'withResponse=${mtu.maxWriteWithResponse}',
        );
        return;
      }
      if (!Platform.isAndroid) {
        _addDeviceLog(remoteId, '[mtu] $remoteId: unsupported platform');
        return;
      }
      final requested = await ElinkBle.setAndroidMtu(remoteId, androidMtu);
      _addDeviceLog(
        remoteId,
        '[setAndroidMtu] $remoteId: '
        'mtu=$androidMtu requested=$requested',
      );
    } catch (error) {
      _addDeviceLog(remoteId, '[mtu] $remoteId: $error');
    }
  }

  /// 更新 Android 指令发送失败重发次数,0 表示关闭。
  Future<void> _setAndroidCommandResendCount(
    int resendCount, {
    String? logRemoteId,
  }) async {
    if (resendCount < 0 || resendCount == _androidCommandResendCount) {
      return;
    }
    final previousCount = _androidCommandResendCount;
    setState(() => _androidCommandResendCount = resendCount);
    try {
      await ElinkBle.setAndroidCommandResendCount(resendCount: resendCount);
      if (logRemoteId != null) {
        _addDeviceLog(logRemoteId, '[androidResend] resendCount=$resendCount');
      }
    } catch (error) {
      if (mounted) {
        setState(() => _androidCommandResendCount = previousCount);
      }
      if (logRemoteId != null) {
        _addDeviceLog(
          logRemoteId,
          '[androidResend] resendCount=$resendCount: $error',
        );
      }
    }
  }

  /// Disconnect the selected BLE device by remoteId and log failures (按 remoteId 断开选中 BLE 设备,并记录失败).
  Future<void> _disconnectDevice(String remoteId) async {
    try {
      await ElinkBle.disconnect(remoteId);
    } catch (error) {
      _addDeviceLog(remoteId, '[disconnect] $remoteId: $error');
    }
  }

  Future<void> _startScan() async {
    try {
      await ElinkBle.startScan(timeout: const Duration(seconds: 10));
    } catch (error) {
      debugPrint('[startScan] $error');
    }
  }

  Future<void> _stopScan() async {
    try {
      await ElinkBle.stopScan();
    } catch (error) {
      debugPrint('[stopScan] $error');
    }
  }

  /// 请求系统打开蓝牙,并立即刷新一次缓存状态。
  Future<void> _openBluetooth() async {
    try {
      await ElinkBle.openBluetooth();
      await ElinkBle.refreshAdapterState();
    } catch (error) {
      debugPrint('[openBluetooth] $error');
    }
  }

  Future<void> _getBmVersion(String remoteId) async {
    try {
      await ElinkBle.getBmVersion(remoteId);
      _addDeviceLog(remoteId, '[tx][getBmVersion] $remoteId: native 0x46');
    } catch (error) {
      _addDeviceLog(remoteId, '[getBmVersion] $remoteId: $error');
    }
  }

  /// Open the WiFi provisioning page with the connected BLE remoteId (使用当前已连接 BLE remoteId 打开 WiFi 配网页面).
  void _openWifiProvisioning(String remoteId) {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (context) => WifiProvisioningPage(initialRemoteId: remoteId),
      ),
    );
  }

  /// 在 sample 的 characteristic changed 回调中处理完整 A7 原始 frame。
  Future<void> _handleA7CharacteristicChanged(
    ElinkCharacteristicEvent event,
  ) async {
    if (event.operation != ElinkCharacteristicOperation.changed) {
      return;
    }
    final frame = ElinkDataProcessor.tryParseA7Frame(event.data);
    if (frame == null) {
      return;
    }
    final mac = _a7DecryptMac(event.remoteId);
    if (mac == null || mac.isEmpty) {
      _addDeviceLog(
        event.remoteId,
        '[a7Decrypt] ${event.remoteId}: missing MAC',
      );
      return;
    }
    try {
      final decrypted = await ElinkDataProcessor.decryptA7Packet(
        mac: mac,
        packet: frame.rawData,
      );
      if (decrypted == null) {
        _addDeviceLog(
          event.remoteId,
          '[a7Decrypt] ${event.remoteId}: decrypt result is null',
        );
        return;
      }
      final cidText = frame.cid == null
          ? '-'
          : '0x${frame.cid!.toRadixString(16).padLeft(4, '0').toUpperCase()}';
      _addDeviceLog(
        event.remoteId,
        '[a7Decrypt][characteristicEvents] ${event.remoteId}: '
        'cid=$cidText payload=${ElinkDataProcessor.formatHex(decrypted)}',
      );
      _addPayloadParseLog(
        source: 'a7Decrypt:characteristicEvents',
        remoteId: event.remoteId,
        payload: decrypted,
      );
    } catch (error) {
      _addDeviceLog(event.remoteId, '[a7Decrypt] ${event.remoteId}: $error');
    }
  }

  /// 获取 A7 解密需要的 little-endian MAC,优先使用扫描广播数据。
  List<int>? _a7DecryptMac(String remoteId) {
    for (final result in _scanResults) {
      if (result.device.remoteId != remoteId) {
        continue;
      }
      final bleData = result.advertisementData.identity;
      if (!bleData.isEmpty && bleData.mac.any((byte) => byte != 0)) {
        return bleData.mac;
      }
    }
    return _littleEndianMacFromRemoteId(remoteId);
  }

  /// 将 Android 常见 MAC remoteId 转为 SDK 解密所需的 little-endian byte 顺序。
  List<int>? _littleEndianMacFromRemoteId(String remoteId) {
    final hex = remoteId.replaceAll(RegExp(r'[^0-9A-Fa-f]'), '');
    if (hex.length != 12) {
      return null;
    }
    final bytes = <int>[];
    for (var index = 0; index < hex.length; index += 2) {
      final byte = int.tryParse(hex.substring(index, index + 2), radix: 16);
      if (byte == null) {
        return null;
      }
      bytes.add(byte);
    }
    return bytes.reversed.toList(growable: false);
  }

  void _addProtocolPacketParseLogs(ElinkProtocolDataPacket packet) {
    switch (packet.protocol) {
      case ElinkProtocolDataType.a6:
        final frame = ElinkDataProcessor.parseA6Frame(
          ElinkDataProcessor.wrapA6Frame(packet.data),
        );
        _addFrameParseLog(
          source: 'protocolDataPackets',
          remoteId: packet.remoteId,
          frame: frame,
        );
        _addPayloadParseLog(
          source: 'protocolDataPackets',
          remoteId: packet.remoteId,
          payload: frame.payload,
        );
        break;
      case ElinkProtocolDataType.a7:
        final cid = packet.deviceType;
        if (cid == null) {
          _addPayloadParseLog(
            source: 'protocolDataPackets',
            remoteId: packet.remoteId,
            payload: packet.data,
            prefix: 'A7 cid is missing, ',
          );
          return;
        }
        final frame = ElinkDataProcessor.parseA7Frame(
          ElinkDataProcessor.wrapA7Frame(cid: cid, payload: packet.data),
        );
        _addFrameParseLog(
          source: 'protocolDataPackets',
          remoteId: packet.remoteId,
          frame: frame,
        );
        _addPayloadParseLog(
          source: 'protocolDataPackets',
          remoteId: packet.remoteId,
          payload: frame.payload,
        );
        break;
    }
  }

  void _addRawFrameParseLogs({
    required String source,
    required String remoteId,
    required Iterable<int> data,
  }) {
    final frame = ElinkDataProcessor.tryParseProtocolFrame(data.toList());
    if (frame == null) {
      return;
    }
    _addFrameParseLog(source: source, remoteId: remoteId, frame: frame);
    _addPayloadParseLog(
      source: source,
      remoteId: remoteId,
      payload: frame.payload,
    );
  }

  void _addFrameParseLog({
    required String source,
    required String remoteId,
    required ElinkProtocolFrame frame,
  }) {
    final cidText = frame.cid == null
        ? '-'
        : '0x${frame.cid!.toRadixString(16).padLeft(4, '0').toUpperCase()}';
    _addDeviceLog(
      remoteId,
      '[parse][$source] $remoteId: '
      'protocol=${frame.protocol.name.toUpperCase()} '
      'cid=$cidText len=${frame.payloadLength} '
      'checksum=0x${frame.checksum.toRadixString(16).padLeft(2, '0').toUpperCase()} '
      'payload=${ElinkDataProcessor.formatHex(frame.payload)}',
    );
  }

  void _addPayloadParseLog({
    required String source,
    required String remoteId,
    required Iterable<int> payload,
    String prefix = '',
  }) {
    final payloads = ElinkDataProcessor.tryParsePayload(
      payload.toList(growable: false),
      parseTlv: _enableTlvParse,
    );
    _addDeviceLog(
      remoteId,
      '[parse][$source] $remoteId: '
      '$prefix${_enableTlvParse ? 'tlv=' : 'payload='}'
      '${_formatPayloads(payloads, parseTlv: _enableTlvParse)}',
    );
  }

  String _formatPayloads(
    List<ElinkPayload>? payloads, {
    required bool parseTlv,
  }) {
    if (payloads == null) {
      return 'invalid';
    }
    if (payloads.isEmpty) {
      return 'empty';
    }
    return payloads
        .map((payload) => _formatPayload(payload, parseTlv))
        .join(' | ');
  }

  String _formatPayload(ElinkPayload payload, bool parseTlv) {
    final data = payload.data.isEmpty
        ? 'empty'
        : ElinkDataProcessor.formatHex(payload.data);
    final length = parseTlv ? ', length: ${payload.length}' : '';
    return '{type: 0x${payload.type.toRadixString(16).padLeft(2, '0').toUpperCase()}'
        '$length, data: $data}';
  }

  /// 记录指定设备 tab 的日志,并限制单设备日志数量。
  void _addDeviceLog(String remoteId, String message) {
    if (!mounted) return;
    if (remoteId.isEmpty) return;
    final timestamp = ExampleTimeUtils.formatTimestamp(DateTime.now());
    setState(() {
      final logs = _deviceLogs.putIfAbsent(remoteId, () => <String>[]);
      logs.add('[$timestamp] $message');
      if (logs.length > _maxLogCount) {
        logs.removeRange(0, logs.length - _maxLogCount);
      }
    });
  }

  /// 从错误 details 中提取 remoteId,用于把错误日志归属到对应设备 tab。
  String? _remoteIdFromError(ElinkBleException error) {
    final details = error.details;
    if (details is ElinkDeviceEvent) {
      return details.remoteId;
    }
    if (details is ElinkServiceDiscoveredEvent) {
      return details.remoteId;
    }
    if (details is ElinkProtocolDataPacket) {
      return details.remoteId;
    }
    if (details is ElinkPassthroughDataPacket) {
      return details.remoteId;
    }
    if (details is ElinkCharacteristicEvent) {
      return details.remoteId;
    }
    if (details is ElinkRssiEvent) {
      return details.remoteId;
    }
    if (details is ElinkMtuEvent) {
      return details.remoteId;
    }
    if (details is ElinkHandshakeEvent) {
      return details.remoteId;
    }
    if (details is ElinkBmVersionEvent) {
      return details.remoteId;
    }
    if (details is Map) {
      final remoteId = details['remoteId']?.toString();
      if (remoteId != null && remoteId.isNotEmpty) {
        return remoteId;
      }
    }
    return null;
  }
}
0
likes
150
points
721
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

ElinkThings BLE SDK plugin for scanning, connecting, and processing Elink BLE data.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_elink_ble

Packages that implement flutter_elink_ble