Initial commit

This commit is contained in:
Tony
2026-04-27 08:00:32 +08:00
commit fbfc24c9e5
20 changed files with 1366 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
/// BLE GATT-server peripheral host adapter for the DALI Toolkit relay feature.
///
/// Usage:
/// ```dart
/// final host = BleRelayHost();
/// if (host.isSupported) {
/// await host.start(
/// advertisedName: 'DALI-Bridge',
/// onClientWrite: (data) async { /* forward to DALI upstream */ },
/// );
/// // Push DALI responses back to connected BLE clients:
/// await host.push(responseBytes);
/// await host.stop();
/// }
/// ```
library ble_relay_host;
export 'src/ble_relay_host.dart';
+101
View File
@@ -0,0 +1,101 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
/// GATT service UUID shared between DALI Toolkit BLE central (client) and the
/// relay host peripheral. Must match the UUID used in `ble.dart` / `ble_web.dart`.
const String kBleRelayServiceUuid = '0000fff0-0000-1000-8000-00805f9b34fb';
/// GATT characteristic UUID for bidirectional DALI frame data.
const String kBleRelayCharacteristicUuid = '0000fff1-0000-1000-8000-00805f9b34fb';
/// Method-channel name (must match Android/iOS/macOS native implementations).
const String _kChannelName = 'com.dalimaster.ble_relay_host';
/// An abstraction over the platform BLE GATT-server implementation.
///
/// On Android (API 21+) this wires into [BluetoothGattServer] + [BluetoothLeAdvertiser].
/// On all other platforms, [isSupported] returns `false` and every method is a no-op.
class BleRelayHost {
BleRelayHost() : _channel = const MethodChannel(_kChannelName);
@visibleForTesting
BleRelayHost.withChannel(MethodChannel channel) : _channel = channel;
final MethodChannel _channel;
StreamSubscription<dynamic>? _eventSub;
Future<void> Function(Uint8List data)? _onClientWrite;
bool _running = false;
bool get isRunning => _running;
/// Returns true if the current platform has a native implementation.
/// On Web this is always false; on non-Android platforms this calls the
/// native `isSupported` method to let the platform decide.
bool get isSupported {
if (kIsWeb) return false;
// Synchronous check is not possible via MethodChannel; instead we rely on
// the static per-platform capability flags defined at the package root.
// See [BleRelayHostCapability.isSupported].
return defaultTargetPlatform == TargetPlatform.android;
}
/// Starts BLE advertising and opens the GATT server.
///
/// [advertisedName] — the Bluetooth device name visible to scanning clients.
/// [onClientWrite] — callback invoked whenever a connected client writes a
/// frame to the relay characteristic. Forward the bytes to
/// the upstream DALI transport.
Future<void> start({
required String advertisedName,
required Future<void> Function(Uint8List data) onClientWrite,
}) async {
if (!isSupported) return;
_onClientWrite = onClientWrite;
// Listen for inbound writes from BLE clients via event channel.
const eventChannel = EventChannel('$_kChannelName/clientWrites');
_eventSub?.cancel();
_eventSub = eventChannel.receiveBroadcastStream().listen((dynamic event) {
if (event is Uint8List) {
_onClientWrite?.call(event);
} else if (event is List) {
_onClientWrite?.call(Uint8List.fromList(event.cast<int>()));
}
});
try {
await _channel.invokeMethod<void>('start', {
'advertisedName': advertisedName,
'serviceUuid': kBleRelayServiceUuid,
'characteristicUuid': kBleRelayCharacteristicUuid,
});
_running = true;
} catch (_) {
await _eventSub?.cancel();
_eventSub = null;
_onClientWrite = null;
rethrow;
}
}
/// Sends [data] as a GATT notification to all connected clients.
Future<void> push(Uint8List data) async {
if (!_running) return;
await _channel.invokeMethod<void>('push', {'data': data});
}
/// Stops advertising, disconnects clients, and closes the GATT server.
Future<void> stop() async {
await _eventSub?.cancel();
_eventSub = null;
_onClientWrite = null;
if (_running) {
await _channel.invokeMethod<void>('stop');
}
_running = false;
}
}