import 'dart:async'; import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:knx_stack/knx_stack.dart'; import 'package:knx_stack_tpuart/knx_stack_tpuart.dart'; void main() { group('KnxTpUartClient', () { test('completes the reset and state handshake', () async { final transport = _FakeKnxTransport(); final client = KnxTpUartClient(transport: transport); final connectFuture = client.connect(); await _flushMicrotasks(); expect(transport.sentFrames, hasLength(1)); expect(transport.sentFrames.single, [0x01]); transport.emit(const [0x03]); await _flushMicrotasks(); expect(transport.sentFrames, hasLength(2)); expect(transport.sentFrames.last, [0x02]); transport.emit(const [0x07]); await connectFuture; expect(client.isConnected, isTrue); }); test('encodes cEMI writes into TP-UART service bytes', () async { final transport = _FakeKnxTransport(); final client = KnxTpUartClient(transport: transport); await _connectClient(client, transport); final sendFuture = client.send(_sampleCemiWrite().toBytes()); await _flushMicrotasks(); expect(transport.sentFrames.last, [ 0x80, 0xbc, 0x81, 0x11, 0x82, 0x01, 0x83, 0x00, 0x84, 0x01, 0x85, 0xe2, 0x86, 0x00, 0x87, 0x80, 0x88, 0x64, 0x49, 0x54, ]); transport.emit(const [0x8b]); await sendFuture; }); test('decodes inbound TP telegrams into cEMI indications', () async { final transport = _FakeKnxTransport(); final client = KnxTpUartClient(transport: transport); await _connectClient(client, transport); final frameFuture = client.inboundFrames.first; final messageFuture = client.inboundMessages.first; transport.emit(const [0xbc, 0x11, 0x01, 0x00, 0x01, 0xe2, 0x00, 0x80, 0x64, 0x54]); expect(await frameFuture, [ 0x29, 0x00, 0xbc, 0xe0, 0x11, 0x01, 0x00, 0x01, 0x02, 0x00, 0x80, 0x64, ]); expect((await messageFuture).destinationAddress.canonical, '0/0/1'); }); }); } Future _connectClient(KnxTpUartClient client, _FakeKnxTransport transport) async { final connectFuture = client.connect(); await _flushMicrotasks(); transport.emit(const [0x03]); await _flushMicrotasks(); transport.emit(const [0x07]); await connectFuture; } Future _flushMicrotasks() async { await Future.delayed(Duration.zero); } CemiLDataReq _sampleCemiWrite() { final controlField2 = ExtendedControlField() ..addressType = AddressType.group ..hopCount = 6 ..eff = ExtendedFrameFormat.pointToPointOrStandardGroupAddressedLDataExtendedFrame; return CemiLDataReq( controlField1: ControlField(0xbc), controlField2: controlField2, sourceAddress: KnxIndividualAddress.parse('1.1.1'), destinationAddress: KnxGroupAddress.parse('0/0/1'), tpdu: TPDU(data: Uint8List.fromList([0x64])), ); } final class _FakeKnxTransport implements KnxTransport { final StreamController _frames = StreamController.broadcast(); final List sentFrames = []; bool _isConnected = false; @override String get transportId => 'fake_tpuart'; @override KnxFeature get feature => KnxFeature.tpuartTransport; @override KnxFeatureSupport get support => const KnxFeatureSupport.supported( feature: KnxFeature.tpuartTransport, platform: KnxPlatform.macos, ); @override bool get isConnected => _isConnected; @override Stream get inboundFrames => _frames.stream; @override Future connect() async { _isConnected = true; } @override Future disconnect() async { _isConnected = false; } @override Future send(Uint8List frame) async { sentFrames.add(Uint8List.fromList(frame)); } void emit(List frame) { _frames.add(Uint8List.fromList(frame)); } }