Initial commit

This commit is contained in:
Tony
2026-04-24 04:08:27 +08:00
commit 29707dcd3a
14 changed files with 977 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
export 'src/knx_usb_hid_support.dart';
export 'src/knx_usb_hid_transport.dart';
+40
View File
@@ -0,0 +1,40 @@
import 'package:knx_stack/knx_stack.dart';
final class KnxUsbHidSupport {
const KnxUsbHidSupport._();
static KnxRuntimeSupport current({KnxPlatform? platform}) {
final resolvedPlatform = platform ?? currentKnxPlatform();
final transportSupport = switch (resolvedPlatform) {
KnxPlatform.android ||
KnxPlatform.macos ||
KnxPlatform.linux ||
KnxPlatform.windows => KnxFeatureSupport.supported(
feature: KnxFeature.usbHidTransport,
platform: resolvedPlatform,
reason: 'usb_hid exposes a native HID implementation on this platform.',
),
KnxPlatform.web => KnxFeatureSupport.conditional(
feature: KnxFeature.usbHidTransport,
platform: resolvedPlatform,
reason: 'Requires a browser with WebHID support and a user-triggered device picker.',
),
KnxPlatform.ios => KnxFeatureSupport.unsupported(
feature: KnxFeature.usbHidTransport,
platform: resolvedPlatform,
reason: 'usb_hid does not provide an iOS implementation.',
),
KnxPlatform.fuchsia ||
KnxPlatform.unknown => KnxFeatureSupport.unsupported(
feature: KnxFeature.usbHidTransport,
platform: resolvedPlatform,
reason: 'No KNX USB HID adapter is implemented for this runtime.',
),
};
return KnxRuntimeSupport.base(platform: resolvedPlatform).merge(
KnxRuntimeSupport(platform: resolvedPlatform, features: [transportSupport]),
);
}
}
+99
View File
@@ -0,0 +1,99 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:knx_stack/knx_stack.dart';
import 'package:usb_hid/usb_hid.dart';
import 'knx_usb_hid_support.dart';
final class KnxUsbHidTransport implements KnxTransport {
KnxUsbHidTransport({
UsbHid? usbHid,
this.filters = const [],
HidDeviceInfo? selectedDevice,
}) : _usbHid = usbHid ?? UsbHid(),
_selectedDevice = selectedDevice;
final UsbHid _usbHid;
final List<HidDeviceFilter> filters;
final StreamController<Uint8List> _frames = StreamController<Uint8List>.broadcast();
HidDeviceInfo? _selectedDevice;
HidDevice? _device;
StreamSubscription<HidInputReport>? _subscription;
bool _isConnected = false;
@override
String get transportId => 'knx_usb_hid';
@override
KnxFeature get feature => KnxFeature.usbHidTransport;
@override
KnxFeatureSupport get support => KnxUsbHidSupport.current().supportFor(feature);
@override
bool get isConnected => _isConnected;
@override
Stream<Uint8List> get inboundFrames => _frames.stream;
HidDeviceInfo? get selectedDevice => _selectedDevice;
Future<List<HidDeviceInfo>> listDevices() {
return _usbHid.listDevices(filters: filters.isEmpty ? null : filters);
}
Future<HidDeviceInfo?> requestDevice() async {
support.requireSupported('requestDevice');
if (filters.isEmpty) {
throw StateError('requestDevice requires at least one HID filter.');
}
_selectedDevice = await _usbHid.requestDevice(filters: filters);
return _selectedDevice;
}
@override
Future<void> connect() async {
support.requireSupported('connect');
if (_isConnected) {
return;
}
final deviceInfo = _selectedDevice;
if (deviceInfo == null) {
throw StateError('No KNX HID device selected. Call requestDevice() or pass selectedDevice first.');
}
final device = await _usbHid.openDevice(deviceInfo);
_device = device;
_subscription = device.inputReports.listen((report) {
_frames.add(Uint8List.fromList(report.data));
});
_isConnected = true;
}
@override
Future<void> disconnect() async {
await _subscription?.cancel();
_subscription = null;
final device = _device;
_device = null;
_isConnected = false;
if (device != null) {
await device.close();
}
}
@override
Future<void> send(Uint8List frame) async {
support.requireSupported('send');
final device = _device;
if (device == null) {
throw StateError('Transport is not connected.');
}
await device.sendOutputReport(frame);
}
}