diff --git a/ConfigRequest.pcapng b/ConfigRequest.pcapng new file mode 100644 index 0000000..2422e94 Binary files /dev/null and b/ConfigRequest.pcapng differ diff --git a/Kaenx.Konnect/Builders/ConnectionRequest.cs b/Kaenx.Konnect/Builders/ConnectionRequest.cs index cda3510..7caf129 100644 --- a/Kaenx.Konnect/Builders/ConnectionRequest.cs +++ b/Kaenx.Konnect/Builders/ConnectionRequest.cs @@ -9,7 +9,7 @@ class ConnectionRequest : IRequestBuilder { private List bytes = new List(); - public void Build(IPEndPoint source, byte communicationChannel) + public void Build(IPEndPoint source, bool isConfig = false) { byte[] header = { 0x06, 0x10, 0x02, 0x05 }; bytes.AddRange(header); @@ -31,10 +31,16 @@ public void Build(IPEndPoint source, byte communicationChannel) bytes.AddRange(port); // IP Adress Port - bytes.Add(0x04); // Request Structure Length - bytes.Add(0x04); // Tunnel Connection - bytes.Add(0x02); // Tunnel Link Layer - bytes.Add(0x00); // Reserved + if(isConfig) + { + bytes.Add(0x02); // Request Structure Length + bytes.Add(0x03); // Config Connection + } else { + bytes.Add(0x04); // Request Structure Length + bytes.Add(0x04); // Tunnel Connection + bytes.Add(0x02); // Tunnel Link Layer + bytes.Add(0x00); // Reserved + } byte[] length = BitConverter.GetBytes((ushort)(bytes.Count + 2)); Array.Reverse(length); diff --git a/Kaenx.Konnect/Classes/BusDevice.cs b/Kaenx.Konnect/Classes/BusDevice.cs index 470db6e..e6d977d 100644 --- a/Kaenx.Konnect/Classes/BusDevice.cs +++ b/Kaenx.Konnect/Classes/BusDevice.cs @@ -2,6 +2,7 @@ using Kaenx.Konnect.Addresses; using Kaenx.Konnect.Builders; using Kaenx.Konnect.Connections; +using Kaenx.Konnect.Exceptions; using Kaenx.Konnect.Messages.Request; using Kaenx.Konnect.Messages.Response; using Kaenx.Konnect.Parser; @@ -23,7 +24,7 @@ public class BusDevice public ManagmentModels ManagmentModel { get; set; } public bool SupportsExtendedFrames { get; set; } = false; - private int MaxFrameLength { get; set; } = 15; + public int MaxFrameLength { get; set; } = 15; public ushort? MaskVersion { get; private set; } = null; private bool _isConnected = false; @@ -32,6 +33,7 @@ public class BusDevice private Dictionary responses = new Dictionary(); private Dictionary acks = new Dictionary(); private Dictionary features; + private int timeoutForData = 4000; private int _seqNum = 0; private int _currentSeqNum @@ -87,7 +89,15 @@ public BusDevice(UnicastAddress address, IKnxConnection conn) acks.Add(i, false); } + public void SetTimeout(int timeout) + { + timeoutForData = timeout; + } + public bool IsConnected() + { + return _isConnected; + } #region Waiters private void _conn_OnTunnelAck(MsgAckRes response) @@ -136,7 +146,7 @@ private async Task WaitForData(int seq, CancellationToken toke await Task.Delay(5); // TODO maybe erhöhen if (token.IsCancellationRequested) - throw new TimeoutException("Zeitüberschreitung beim Warten auf antwort"); + throw new TimeoutException("Zeitüberschreitung beim Warten auf Antwort"); var resp = responses[seq]; responses.Remove(seq); @@ -157,6 +167,11 @@ private async Task WaitForAck(int seq, CancellationToken token) #region Helper Functions + public void SetMaxFrameLength(int maxFrameLength) + { + MaxFrameLength = maxFrameLength; + } + private async Task GetMaskVersion() { if (_mask != "") return _mask; @@ -216,6 +231,12 @@ public async Task IsReachable() /// public async Task Connect(bool onlyConnect = false) { + if(_isConnected) + await Disconnect(); //reset the connection + + _currentSeqNum = 0; + _lastNumb = -1; + MsgConnectReq message = new MsgConnectReq(_address); await _conn.Send(message); @@ -224,12 +245,9 @@ public async Task Connect(bool onlyConnect = false) _conn.OnTunnelAck += _conn_OnTunnelAck; await Task.Delay(300); - _isConnected = true; - var x = await DeviceDescriptorRead(); - //return; if (onlyConnect) { MaxFrameLength = 15; @@ -246,8 +264,10 @@ public async Task Connect(bool onlyConnect = false) } catch { - MaxFrameLength = 12; - //Debug.WriteLine("Gerät hat die Property MaxAPDU nicht. Es wird von 15 ausgegangen"); + MaxFrameLength = 15; + Debug.WriteLine("Gerät hat die Property MaxAPDU nicht. Es wird von 15 ausgegangen"); + await Disconnect(); + await Connect(true); } } @@ -277,7 +297,7 @@ public async Task Disconnect() public async Task Restart() { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); MsgRestartReq message = new MsgRestartReq(_address); message.SequenceNumber = _currentSeqNum++; @@ -297,7 +317,7 @@ public async Task Restart() public async Task ResourceWrite(string resourceId, byte[] data) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); string maskId = await GetMaskVersion(); XDocument master = GetKnxMaster(); @@ -352,7 +372,7 @@ public async Task ResourceWrite(string resourceId, byte[] data) public async Task ResourceAddress(string ressourceId) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); if(await HasResource(ressourceId + "Ptr")) { @@ -389,14 +409,14 @@ public Task PropertyWriteResponse(byte objIdx, byte propId, byte[] data) public async Task PropertyWriteResponse(byte objIdx, byte propId, byte[] data) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); MsgPropertyWriteReq message = new MsgPropertyWriteReq(objIdx, propId, data, _address); message.SequenceNumber = _currentSeqNum++; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(10000); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); MsgPropertyReadRes resp = (MsgPropertyReadRes)await WaitForData(message.SequenceNumber, tokenS.Token); return resp.Get(); } @@ -425,7 +445,7 @@ public async Task ResourceRead(string resourceId, bool onlyAddress = fal public async Task ResourceRead(string resourceId, bool onlyAddress = false) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); string maskId = await GetMaskVersion(); XDocument master = GetKnxMaster(); @@ -437,7 +457,7 @@ public async Task ResourceRead(string resourceId, bool onlyAddress = false } catch { - throw new NotSupportedException("Mask '" + maskId + "' does not support this Resource: " + resourceId); + throw new Exceptions.NotSupportedException("Mask '" + maskId + "' does not support this Resource: " + resourceId); } XElement loc = prop.Element(XName.Get("Location", master.Root.Name.NamespaceName)); @@ -486,9 +506,9 @@ public async Task ResourceRead(string resourceId, bool onlyAddress = false /// Anzahl der zu lesenden Bytes /// Startindex /// Property Wert - public async Task PropertyRead(byte objIdx, byte propId, int timeout = 4000) + public async Task PropertyRead(byte objIdx, byte propId) { - return await PropertyRead(objIdx, propId, timeout); + return await PropertyRead(objIdx, propId); } /// @@ -500,17 +520,17 @@ public async Task PropertyRead(byte objIdx, byte propId, int timeout = 4 /// Startindex /// Property Wert /// - public async Task PropertyRead(byte objIdx, byte propId, int timeout = 4000) + public async Task PropertyRead(byte objIdx, byte propId) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); //Debug.WriteLine("PropRead:" + _currentSeqNum); MsgPropertyReadReq message = new MsgPropertyReadReq(objIdx, propId, _address); message.SequenceNumber = _currentSeqNum++; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(timeout); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); //Debug.WriteLine("Wating for " + objIdx + "/" + propId + ": " + message.SequenceNumber); MsgPropertyReadRes resp = (MsgPropertyReadRes)await WaitForData(message.SequenceNumber, tokenS.Token); //Debug.WriteLine("Ended waiting"); @@ -527,14 +547,14 @@ public async Task PropertyRead(byte objIdx, byte propId, int timeout = 400 public async Task PropertyDescriptionRead(byte objIdx, byte propId) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); //Debug.WriteLine("PropDescriptionRead:" + _currentSeqNum); MsgPropertyDescriptionReq message = new MsgPropertyDescriptionReq(objIdx, propId, 0, _address); message.SequenceNumber = _currentSeqNum++; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(10000); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); //Debug.WriteLine("Wating for Description " + objIdx + "/" + propId + ": " + message.SequenceNumber); MsgPropertyDescriptionRes resp = (MsgPropertyDescriptionRes)await WaitForData(message.SequenceNumber, tokenS.Token); //Debug.WriteLine("Ended waiting"); @@ -551,10 +571,10 @@ public async Task PropertyDescriptionRead(byte objIdx /// Daten die geschrieben werden sollen /// /// - public async Task PropertyWrite(byte objIdx, byte propId, byte[] data, bool waitForResp = false, int timeout = 4000) + public async Task PropertyWrite(byte objIdx, byte propId, byte[] data, bool waitForResp = false) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); var seq1 = _currentSeqNum++; @@ -562,7 +582,7 @@ public async Task PropertyWrite(byte objIdx, byte propId, byte[] data, bool wait message.SequenceNumber = seq1; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(timeout); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); if (waitForResp) await WaitForData(message.SequenceNumber, tokenS.Token); @@ -578,10 +598,9 @@ public async Task PropertyWrite(byte objIdx, byte propId, byte[] data, bool wait /// Daten die übergeben werden sollen /// /// - public async Task InvokeFunctionProperty(byte objIdx, byte propId, byte[] data, bool waitForResp = false, int timeout = 4000) - { + public async Task InvokeFunctionProperty(byte objIdx, byte propId, byte[] data, bool waitForResp = false) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); if(data == null) @@ -593,7 +612,7 @@ public async Task InvokeFunctionProperty(byte objId message.SequenceNumber = seq1; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(timeout); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); if (waitForResp) { var response = (MsgFunctionPropertyStateRes)await WaitForData(message.SequenceNumber, tokenS.Token); @@ -615,10 +634,10 @@ public async Task InvokeFunctionProperty(byte objId /// Daten die übergeben werden sollen /// /// - public async Task ReadFunctionProperty(byte objIdx, byte propId, byte[] data, bool waitForResp = false, int timeout = 4000) + public async Task ReadFunctionProperty(byte objIdx, byte propId, byte[] data, bool waitForResp = false) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); if(data == null) data = new byte[0]; @@ -629,7 +648,7 @@ public async Task ReadFunctionProperty(byte objIdx, message.SequenceNumber = seq1; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(timeout); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); if (waitForResp) return (MsgFunctionPropertyStateRes)await WaitForData(message.SequenceNumber, tokenS.Token); @@ -652,7 +671,7 @@ public async Task ReadFunctionProperty(byte objIdx, public async Task MemoryWrite(int address, byte[] databytes, bool verify = false) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); List datalist = databytes.ToList(); int currentPosition = address; @@ -709,7 +728,7 @@ public async Task MemoryWrite(int address, byte[] databytes, bool verify = false var seq = message.SequenceNumber; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(10000); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); if (verify) { if (verifyMode == VerifyMode.NotSupported) @@ -758,7 +777,7 @@ public async Task MemoryRead(int address, int length) public async Task MemoryRead(int address, int length) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); List readed = new List(); int currentPosition = address; @@ -781,7 +800,7 @@ public async Task MemoryRead(int address, int length) await _conn.Send(msg); //Debug.WriteLine("Warten auf: " + seq); - CancellationTokenSource tokenS = new CancellationTokenSource(10000); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); IMessageResponse resp = await WaitForData(msg.SequenceNumber, tokenS.Token); readed.AddRange(resp.Raw.Skip(2)); currentPosition += toRead; @@ -827,13 +846,13 @@ public async Task MemoryRead(int address, int length) public async Task Authorize(uint key) { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); MsgAuthorizeReq message = new MsgAuthorizeReq(key, _address); message.SequenceNumber = _currentSeqNum++; CheckForData(message.SequenceNumber); await _conn.Send(message); - CancellationTokenSource tokenS = new CancellationTokenSource(10000); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); MsgAuthorizeRes resp = (MsgAuthorizeRes)await WaitForData(message.SequenceNumber, tokenS.Token); return resp.Level; } @@ -846,14 +865,14 @@ public async Task Authorize(uint key) public async Task DeviceDescriptorRead() { if(!_isConnected) - throw new Exception("Device is not connected"); + throw new DeviceNotConnectedException(); MsgDescriptorReadReq message = new MsgDescriptorReadReq(_address); message.SequenceNumber = _currentSeqNum++; CheckForData(message.SequenceNumber); await _conn.Send(message); //Debug.WriteLine("Warten auf: " + seq); - CancellationTokenSource tokenS = new CancellationTokenSource(10000); + CancellationTokenSource tokenS = new CancellationTokenSource(timeoutForData); //Todo MsgDeviceDescriptorReadRes convert benutzen IMessageResponse resp = await WaitForData(message.SequenceNumber, tokenS.Token); MaskVersion = (ushort)(resp.Raw[0] << 8 | resp.Raw[1]); diff --git a/Kaenx.Konnect/Classes/ReceiverParserDispatcher.cs b/Kaenx.Konnect/Classes/ReceiverParserDispatcher.cs index a51a872..0b765b0 100644 --- a/Kaenx.Konnect/Classes/ReceiverParserDispatcher.cs +++ b/Kaenx.Konnect/Classes/ReceiverParserDispatcher.cs @@ -9,6 +9,16 @@ namespace Kaenx.Konnect.Classes { class ReceiverParserDispatcher { + private static ReceiverParserDispatcher _instance; + public static ReceiverParserDispatcher Instance + { + get { + if( _instance == null) + _instance = new ReceiverParserDispatcher(); + return _instance; + } + } + private readonly List _responseParsers; public ReceiverParserDispatcher() @@ -23,7 +33,6 @@ public ReceiverParserDispatcher() parsers.Add(type); } - foreach (Type t in parsers) { IReceiveParser parser = (IReceiveParser)Activator.CreateInstance(t); @@ -40,7 +49,7 @@ public IParserMessage Build(byte[] responseBytes) //Console.WriteLine($"ServiceType: {serviceTypeIdentifier} {responseBytes[2]:X}-{responseBytes[3]:X}"); - IReceiveParser parser = _responseParsers.AsQueryable().SingleOrDefault(x => x.ServiceTypeIdentifier == serviceTypeIdentifier); + IReceiveParser parser = _responseParsers.SingleOrDefault(x => x.ServiceTypeIdentifier == serviceTypeIdentifier); IParserMessage result = parser?.Build(headerLength, protocolVersion, totalLength, responseBytes.Skip(6).ToArray()); return result; } diff --git a/Kaenx.Konnect/Connections/IKnxConnection.cs b/Kaenx.Konnect/Connections/IKnxConnection.cs index d74963b..baf3327 100644 --- a/Kaenx.Konnect/Connections/IKnxConnection.cs +++ b/Kaenx.Konnect/Connections/IKnxConnection.cs @@ -12,7 +12,7 @@ namespace Kaenx.Konnect.Connections { - public interface IKnxConnection + public interface IKnxConnection : IDisposable { public delegate void TunnelRequestHandler(IMessageRequest message); public event TunnelRequestHandler OnTunnelRequest; @@ -21,11 +21,6 @@ public interface IKnxConnection public delegate void TunnelAckHandler(MsgAckRes message); public event TunnelAckHandler OnTunnelAck; - public delegate void SearchResponseHandler(MsgSearchRes message); - public event SearchResponseHandler OnSearchResponse; - public delegate void SearchRequestHandler(MsgSearchReq message); - public event SearchRequestHandler OnSearchRequest; - public delegate void ConnectionChangedHandler(bool isConnected); public event ConnectionChangedHandler ConnectionChanged; @@ -45,6 +40,11 @@ public interface IKnxConnection /// public UnicastAddress PhysicalAddress { get; set; } + /// + /// Returns the max APDU length of the interface + /// + public int MaxFrameLength { get; set; } + /// /// Connects the interface to the bus. /// diff --git a/Kaenx.Konnect/Connections/KnxIpRouting.cs b/Kaenx.Konnect/Connections/KnxIpRouting.cs index fd88ce6..722316d 100644 --- a/Kaenx.Konnect/Connections/KnxIpRouting.cs +++ b/Kaenx.Konnect/Connections/KnxIpRouting.cs @@ -28,37 +28,26 @@ public class KnxIpRouting : IKnxConnection public event TunnelRequestHandler OnTunnelRequest; public event TunnelResponseHandler OnTunnelResponse; public event TunnelAckHandler OnTunnelAck; - public event SearchResponseHandler OnSearchResponse; - public event SearchRequestHandler OnSearchRequest; public event ConnectionChangedHandler ConnectionChanged; public bool IsConnected { get; set; } public ConnectionErrors LastError { get; set; } public UnicastAddress PhysicalAddress { get; set; } + public int MaxFrameLength { get; set; } = 254; private ProtocolTypes CurrentType { get; set; } = ProtocolTypes.cEmi; - private bool StopProcessing = false; private byte _sequenceCounter = 0; private readonly IPEndPoint _sendEndPoint; - private List _udpList = new List(); - private readonly BlockingCollection _sendMessages; + private List _clients = new List(); + private readonly Queue _sendMessages; private readonly ReceiverParserDispatcher _receiveParserDispatcher; - -/* - public KnxIpRouting(string ip = "224.0.23.12", int port = 3671) - { - _receiveParserDispatcher = new ReceiverParserDispatcher(); - _sendMessages = new BlockingCollection(); - - Init(ip, port); - } -*/ + private CancellationTokenSource tokenSource; public KnxIpRouting(UnicastAddress physicalAddress, string ip = "224.0.23.12", int port = 3671) { _receiveParserDispatcher = new ReceiverParserDispatcher(); - _sendMessages = new BlockingCollection(); + _sendMessages = new Queue(); PhysicalAddress = physicalAddress; _sendEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); @@ -67,19 +56,40 @@ public KnxIpRouting(UnicastAddress physicalAddress, string ip = "224.0.23.12", i private void Init(string ip, int port) { - /*UdpClient client = new UdpClient(); - - client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - client.Client.Bind(new IPEndPoint(IPAddress.Any, port)); - //client.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, 100663296); - client.JoinMulticastGroup(IPAddress.Parse(ip), IPAddress.Any); - client.MulticastLoopback = true; - client.Client.MulticastLoopback = true; - _udpList.Add(client); - - ProcessSendMessages(); - ProcessReceivingMessages(client); - */ +// https://stackoverflow.com/questions/61661301/c-sharp-receive-multicast-udp-in-multiple-programs-on-the-same-machine +// https://www.winsocketdotnetworkprogramming.com/clientserversocketnetworkcommunication8l.html +// https://github.com/ChrisTTian667/knx-dotnet/blob/main/Knx/KnxNetIp/KnxNetIpRoutingClient.cs#L82 +// https://github.com/lifeemotions/knx.net/blob/master/src/KNXLib/KnxConnectionRouting.cs#L75 + + // UdpClient _udpClient = new UdpClient + // { + // MulticastLoopback = false, + // ExclusiveAddressUse = false + // }; + // _udpClient.Client.MulticastLoopback = false; + + // _udpClient.Client.SetSocketOption( + // SocketOptionLevel.Socket, + // SocketOptionName.ReuseAddress, + // true); + + // _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, 3671)); + // _udpClient.JoinMulticastGroup(IPAddress.Parse("224.0.23.12"), IPAddress.Any); + // _udpList.Add(_udpClient); + + // IEnumerable ipv4Addresses = + // Dns + // .GetHostAddresses(Dns.GetHostName()) + // .Where(i => i.AddressFamily == AddressFamily.InterNetwork); + + // foreach (IPAddress localIp in ipv4Addresses) + // { + // var client = new UdpClient(new IPEndPoint(localIp, 3671)); + // client.Client.MulticastLoopback = false; + // client.MulticastLoopback = false; + // _udpList.Add(client); + // client.JoinMulticastGroup(IPAddress.Parse("224.0.23.12"), localIp); + // } NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); @@ -91,22 +101,24 @@ private void Init(string ip, int port) if (ipprops.MulticastAddresses.Count == 0 // most of VPN adapters will be skipped || !adapter.SupportsMulticast // multicast is meaningless for this type of connection || OperationalStatus.Up != adapter.OperationalStatus) // this adapter is off or not connected + { + Debug.WriteLine("Skipped " + adapter.Name + " maybe vpn or off"); continue; + } + IPv4InterfaceProperties p = ipprops.GetIPv4Properties(); - if (p == null) continue; // IPv4 is not configured on this adapter - int index = IPAddress.HostToNetworkOrder(p.Index); - - IPAddress addr = adapter.GetIPProperties().UnicastAddresses.Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork).Single().Address; - UdpClient _udpClient = new UdpClient(); - _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - _udpClient.Client.Bind(new IPEndPoint(addr, 3671)); - _udpClient.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, index); - _udpClient.JoinMulticastGroup(IPAddress.Parse("224.0.23.12"), IPAddress.Any); - _udpClient.MulticastLoopback = true; - _udpClient.Client.MulticastLoopback = true; - _udpList.Add(_udpClient); - - Debug.WriteLine("Binded to " + adapter.Name + " - " + addr.ToString() + " - 3671 -> " + index); + if (null == p) // IPv4 is not configured on this adapter + { + Debug.WriteLine("Skipped " + adapter.Name + " ip4 not configured"); + continue; + } + + IPAddress localIp = ipprops.UnicastAddresses.Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork).Single().Address; + + UdpConnection udp = new UdpConnection(localIp, port, new IPEndPoint(IPAddress.Parse(ip), port)); + _clients.Add(udp); + + Debug.WriteLine("Binded to " + adapter.Name + " - " + localIp.ToString() + " - " + 3671 + " -> " + udp.InterfaceIndex); } catch (Exception ex) { @@ -115,25 +127,10 @@ private void Init(string ip, int port) } } - ProcessSendMessages(); - - foreach (UdpClient client in _udpList) - ProcessReceivingMessages(client); - IsConnected = true; ConnectionChanged?.Invoke(true); } - public static int GetFreePort() - { - TcpListener l = new TcpListener(IPAddress.Loopback, 0); - l.Start(); - int port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; - } - - public Task Send(byte[] data, byte sequence) { List xdata = new List(); @@ -147,15 +144,14 @@ public Task Send(byte[] data, byte sequence) xdata.AddRange(data); - _sendMessages.Add(xdata.ToArray()); + _sendMessages.Enqueue(xdata.ToArray()); return Task.CompletedTask; } - public Task Send(byte[] data, bool ignoreConnected = false) { - _sendMessages.Add(data); + _sendMessages.Enqueue(data); return Task.CompletedTask; } @@ -163,7 +159,7 @@ public Task Send(IMessage message, bool ignoreConnected = false) { byte seq = _sequenceCounter++; message.SequenceCounter = seq; - _sendMessages.Add(message); + _sendMessages.Enqueue(message); return Task.FromResult(seq); } @@ -177,12 +173,23 @@ public void Search() public Task Connect() { //Nothing to do here + foreach(UdpConnection conn in _clients) + conn.OnReceived += KnxMessageReceived; + + tokenSource = new CancellationTokenSource(); + Task.Run(ProcessSendMessages, tokenSource.Token); + return Task.CompletedTask; } public Task Disconnect() { //Nothing to do here + foreach(UdpConnection conn in _clients) + conn.OnReceived -= KnxMessageReceived; + + tokenSource.Cancel(); + return Task.CompletedTask; } @@ -191,224 +198,219 @@ public Task SendStatusReq() return Task.FromResult(true); } - private void ProcessReceivingMessages(UdpClient _udpClient) + private void KnxMessageReceived(UdpConnection sender, IParserMessage parserMessage) { - Debug.WriteLine("Höre jetzt auf: " + (_udpClient.Client.LocalEndPoint as IPEndPoint)?.Port); - Task.Run(async () => + try { - int rofl = 0; - try + switch (parserMessage) { - while (!StopProcessing) - { - rofl++; - var result = await _udpClient.ReceiveAsync(); - var knxResponse = _receiveParserDispatcher.Build(result.Buffer); - if(knxResponse == null) continue; + case Responses.RoutingResponse tunnelResponse: + if (tunnelResponse.DestinationAddress.ToString() != PhysicalAddress.ToString()) + { + Debug.WriteLine("Telegram erhalten das nicht mit der Adresse selbst zu tun hat!"); + Debug.WriteLine("Typ: " + tunnelResponse.APCI); + Debug.WriteLine("Eigene Adresse: " + PhysicalAddress.ToString()); + Debug.WriteLine("Adressiert an: " + tunnelResponse.DestinationAddress.ToString()); + break; + } + Debug.WriteLine($"Received: {sender.Interface.Name} {tunnelResponse.APCI}"); - switch (knxResponse) + if (tunnelResponse.APCI.ToString().EndsWith("Response")) + { + List data = new List() { 0x11, 0x00 }; + Builders.TunnelRequest builder = new Builders.TunnelRequest(); + builder.Build(PhysicalAddress, tunnelResponse.SourceAddress, ApciTypes.Ack, tunnelResponse.SequenceNumber); + data.AddRange(builder.GetBytes()); + _=Send(data.ToArray(), _sequenceCounter); + _sequenceCounter++; + + } + else if (tunnelResponse.APCI == ApciTypes.Ack) { - case Responses.RoutingResponse tunnelResponse: - if (tunnelResponse.DestinationAddress.ToString() != PhysicalAddress.ToString()) - { - Debug.WriteLine("Telegram erhalten das nicht mit der Adresse selbst zu tun hat!"); - Debug.WriteLine("Typ: " + tunnelResponse.APCI); - Debug.WriteLine("Eigene Adresse: " + PhysicalAddress.ToString()); - Debug.WriteLine("Adressiert an: " + tunnelResponse.DestinationAddress.ToString()); - break; - } - - if (tunnelResponse.APCI.ToString().EndsWith("Response")) - { - List data = new List() { 0x11, 0x00 }; - Builders.TunnelRequest builder = new Builders.TunnelRequest(); - builder.Build(PhysicalAddress, tunnelResponse.SourceAddress, ApciTypes.Ack, tunnelResponse.SequenceNumber); - data.AddRange(builder.GetBytes()); - _=Send(data.ToArray(), _sequenceCounter); - _sequenceCounter++; - - } - else if (tunnelResponse.APCI == ApciTypes.Ack) - { - OnTunnelAck?.Invoke(new MsgAckRes() - { - //ChannelId = tunnelResponse.CommunicationChannel, - //SequenceCounter = tunnelResponse.SequenceCounter, - SequenceNumber = tunnelResponse.SequenceNumber, - SourceAddress = tunnelResponse.SourceAddress, - DestinationAddress = tunnelResponse.DestinationAddress - }); - break; - } - - List temp = new List(); - var q = from t in Assembly.GetExecutingAssembly().GetTypes() - where t.IsClass && t.IsNested == false && (t.Namespace == "Kaenx.Konnect.Messages.Response" || t.Namespace == "Kaenx.Konnect.Messages.Request") - select t; - - IMessage message = null; - - foreach (Type t in q.ToList()) - { - IMessage resp = (IMessage)Activator.CreateInstance(t); - - if (resp.ApciType == tunnelResponse.APCI) - { - message = resp; - break; - } - } - - if (message == null) - { - //throw new Exception("Kein MessageParser für den APCI " + tunnelResponse.APCI); - message = new MsgDefaultRes() - { - ApciType = tunnelResponse.APCI - }; - Debug.WriteLine("Kein MessageParser für den APCI " + tunnelResponse.APCI); - } - - message.Raw = tunnelResponse.Data; - //message.ChannelId = tunnelResponse.CommunicationChannel; - //message.SequenceCounter = tunnelResponse.SequenceCounter; - message.SequenceNumber = tunnelResponse.SequenceNumber; - message.SourceAddress = tunnelResponse.SourceAddress; - message.DestinationAddress = tunnelResponse.DestinationAddress; - - //routing is only allowed with cemi - message.ParseDataCemi(); - - if (tunnelResponse.APCI.ToString().EndsWith("Response")) - OnTunnelResponse?.Invoke(message as IMessageResponse); - else - OnTunnelRequest?.Invoke(message as IMessageRequest); + OnTunnelAck?.Invoke(new MsgAckRes() + { + //ChannelId = tunnelResponse.CommunicationChannel, + //SequenceCounter = tunnelResponse.SequenceCounter, + SequenceNumber = tunnelResponse.SequenceNumber, + SourceAddress = tunnelResponse.SourceAddress, + DestinationAddress = tunnelResponse.DestinationAddress + }); + break; + } + + + List temp = new List(); + var q = from t in Assembly.GetExecutingAssembly().GetTypes() + where t.IsClass && t.IsNested == false && (t.Namespace == "Kaenx.Konnect.Messages.Response" || t.Namespace == "Kaenx.Konnect.Messages.Request") + select t; + + IMessage message = null; + foreach (Type t in q.ToList()) + { + IMessage resp = (IMessage)Activator.CreateInstance(t); + + if (resp.ApciType == tunnelResponse.APCI) + { + message = resp; break; + } + } - case SearchRequest searchRequest: - { - MsgSearchReq msg = new MsgSearchReq(searchRequest.responseBytes); - msg.ParseDataCemi(); - OnSearchRequest?.Invoke(msg); - break; - } - - case SearchResponse searchResponse: - { - MsgSearchRes msg = new MsgSearchRes(searchResponse.responseBytes); - msg.ParseDataCemi(); - OnSearchResponse?.Invoke(msg); - break; - } - default: - throw new Exception("Not handled Telegram Type: " + knxResponse.GetType().ToString()); + if (message == null) + { + //throw new Exception("Kein MessageParser für den APCI " + tunnelResponse.APCI); + message = new MsgDefaultRes() + { + ApciType = tunnelResponse.APCI + }; + Debug.WriteLine("Kein MessageParser für den APCI " + tunnelResponse.APCI); } - } - Debug.WriteLine("Stopped Processing Messages " + _udpClient.Client.LocalEndPoint.ToString()); - _udpClient.Close(); - _udpClient.Dispose(); - } - catch (Exception ex) - { - Debug.WriteLine("Processing Messages Exception:" + ex.Message); - } - }); - } + message.Raw = tunnelResponse.Data; + //message.ChannelId = tunnelResponse.CommunicationChannel; + //message.SequenceCounter = tunnelResponse.SequenceCounter; + message.SequenceNumber = tunnelResponse.SequenceNumber; + message.SourceAddress = tunnelResponse.SourceAddress; + message.DestinationAddress = tunnelResponse.DestinationAddress; - private void ProcessSendMessages() - { - Task.Run(() => - { - foreach (var sendMessage in _sendMessages.GetConsumingEnumerable()) - { - if (sendMessage is byte[]) - { - Debug.WriteLine("Sending bytes"); - byte[] data = sendMessage as byte[]; - foreach (UdpClient client in _udpList) - client.SendAsync(data, data.Length, _sendEndPoint); - } - else if (sendMessage is MsgSearchReq) - { - MsgSearchReq message = sendMessage as MsgSearchReq; + //routing is only allowed with cemi + message.ParseDataCemi(); - foreach(UdpClient _udp in _udpList) - { - message.Endpoint = _udp.Client.LocalEndPoint as IPEndPoint; - byte[] xdata; + if (tunnelResponse.APCI.ToString().EndsWith("Response")) + OnTunnelResponse?.Invoke(message as IMessageResponse); + else + OnTunnelRequest?.Invoke(message as IMessageRequest); - switch (CurrentType) - { - case ProtocolTypes.Emi1: - xdata = message.GetBytesEmi1(); - break; + break; - case ProtocolTypes.Emi2: - xdata = message.GetBytesEmi2(); //Todo check diffrences to emi1 - //xdata.AddRange(message.GetBytesEmi2()); - break; - case ProtocolTypes.cEmi: - xdata = message.GetBytesCemi(); - break; - default: - throw new Exception("Unbekanntes Protokoll"); - } + case SearchRequest searchRequest: + { + MsgSearchReq msg = new MsgSearchReq(searchRequest.responseBytes); + msg.ParseDataCemi(); + OnTunnelRequest?.Invoke(msg); + break; + } - _udp.SendAsync(xdata, xdata.Length, _sendEndPoint); + case SearchResponse searchResponse: + { + MsgSearchRes msg = new MsgSearchRes(searchResponse.responseBytes); + msg.ParseDataCemi(); + OnTunnelResponse?.Invoke(msg); + break; } - } else if(sendMessage is IMessage) { - Debug.WriteLine("Sending IMessage " + sendMessage.GetType()); - IMessage message = sendMessage as IMessage; - message.SourceAddress = PhysicalAddress; - List xdata = new List(); - - //KNX/IP Header - xdata.Add(0x06); //Header Length - xdata.Add(0x10); //Protokoll Version 1.0 - xdata.Add(0x05); //Service Identifier Family: Tunneling - xdata.Add(0x30); //Service Identifier Type: Request - xdata.AddRange(new byte[] { 0x00, 0x00 }); //Total length. Set later + + default: + throw new Exception("Not handled Telegram Type: " + parserMessage.GetType().ToString()); + } + } catch (Exception ex) + { + Debug.WriteLine("Exception ProcessSendMessage: " + ex.Message); + } + } + + private void ProcessSendMessages() + { + while(!tokenSource.IsCancellationRequested) + { + if(_sendMessages.Count == 0) + continue; + + var sendMessage = _sendMessages.Dequeue(); + + if (sendMessage is byte[]) + { + Debug.WriteLine("Sending bytes"); + byte[] data = sendMessage as byte[]; + foreach (UdpConnection client in _clients) + _ = client.SendAsync(data); + } + else if (sendMessage is MsgSearchReq) + { + MsgSearchReq message = sendMessage as MsgSearchReq; + + foreach(UdpConnection _udp in _clients) + { + message.Endpoint = _udp.GetLocalEndpoint(); + byte[] xdata; switch (CurrentType) { case ProtocolTypes.Emi1: - xdata.AddRange(message.GetBytesEmi1()); + xdata = message.GetBytesEmi1(); break; case ProtocolTypes.Emi2: - xdata.AddRange(message.GetBytesEmi1()); //Todo check diffrences between emi1 - //xdata.AddRange(message.GetBytesEmi2()); + xdata = message.GetBytesEmi2(); //Todo check diffrences to emi1 + //xdata.AddRange(message.GetBytesEmi2()); break; case ProtocolTypes.cEmi: - xdata.AddRange(message.GetBytesCemi()); + xdata = message.GetBytesCemi(); break; default: throw new Exception("Unbekanntes Protokoll"); } - byte[] length = BitConverter.GetBytes((ushort)(xdata.Count)); - Array.Reverse(length); - xdata[4] = length[0]; - xdata[5] = length[1]; - - //_udp.SendAsync(xdata.ToArray(), xdata.Count, _sendEndPoint); - - foreach (UdpClient client in _udpList) - client.SendAsync(xdata.ToArray(), xdata.Count, _sendEndPoint); + _ = _udp.SendAsync(xdata); } - else + } else if(sendMessage is IMessage) { + Debug.WriteLine("Sending IMessage " + sendMessage.GetType()); + IMessage message = sendMessage as IMessage; + message.SourceAddress = PhysicalAddress; + List xdata = new List(); + + //KNX/IP Header + xdata.Add(0x06); //Header Length + xdata.Add(0x10); //Protokoll Version 1.0 + xdata.Add(0x05); //Service Identifier Family: Tunneling + xdata.Add(0x30); //Service Identifier Type: Request + xdata.AddRange(new byte[] { 0x00, 0x00 }); //Total length. Set later + + switch (CurrentType) { - throw new Exception("Unbekanntes Element in SendQueue! " + sendMessage.GetType().FullName); + case ProtocolTypes.Emi1: + xdata.AddRange(message.GetBytesEmi1()); + break; + + case ProtocolTypes.Emi2: + xdata.AddRange(message.GetBytesEmi1()); //Todo check diffrences between emi1 + //xdata.AddRange(message.GetBytesEmi2()); + break; + + case ProtocolTypes.cEmi: + xdata.AddRange(message.GetBytesCemi()); + break; + + default: + throw new Exception("Unbekanntes Protokoll"); } + + byte[] length = BitConverter.GetBytes((ushort)(xdata.Count)); + Array.Reverse(length); + xdata[4] = length[0]; + xdata[5] = length[1]; + + foreach (UdpConnection client in _clients) + _ = client.SendAsync(xdata.ToArray()); + } + else + { + throw new Exception("Unbekanntes Element in SendQueue! " + sendMessage.GetType().FullName); } - }); + } + } + + public void Dispose() + { + Disconnect(); + + foreach (UdpConnection client in _clients) + client.Dispose(); } } } \ No newline at end of file diff --git a/Kaenx.Konnect/Connections/KnxIpSearch.cs b/Kaenx.Konnect/Connections/KnxIpSearch.cs new file mode 100644 index 0000000..bab7f40 --- /dev/null +++ b/Kaenx.Konnect/Connections/KnxIpSearch.cs @@ -0,0 +1,371 @@ +using Kaenx.Konnect.Addresses; +using Kaenx.Konnect.Builders; +using Kaenx.Konnect.Classes; +using Kaenx.Konnect.Messages; +using Kaenx.Konnect.Messages.Request; +using Kaenx.Konnect.Messages.Response; +using Kaenx.Konnect.Responses; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Reflection; +using System.Reflection.Metadata.Ecma335; +using System.Threading; +using System.Threading.Tasks; +using static Kaenx.Konnect.Connections.IKnxConnection; + +namespace Kaenx.Konnect.Connections +{ + public class KnxIpSearch : IKnxConnection + { + public delegate void SearchResponseHandler(MsgSearchRes message, NetworkInterface netInterface, int netIndex); + public delegate void SearchRequestHandler(MsgSearchReq message); + + public event TunnelRequestHandler OnTunnelRequest; + public event TunnelResponseHandler OnTunnelResponse; + public event TunnelAckHandler OnTunnelAck; + public event SearchResponseHandler OnSearchResponse; + public event SearchRequestHandler OnSearchRequest; + public event ConnectionChangedHandler ConnectionChanged; + + public bool IsConnected { get; set; } + public ConnectionErrors LastError { get; set; } + public UnicastAddress PhysicalAddress { get; set; } + public int MaxFrameLength { get; set; } = 15; + + private ProtocolTypes CurrentType { get; set; } = ProtocolTypes.cEmi; + private byte _communicationChannel; + private bool StopProcessing = false; + private byte _sequenceCounter = 0; + + private readonly IPEndPoint _sendEndPoint; + private List _clients = new List(); + private readonly BlockingCollection _sendMessages; + + private List _receivedAcks; + private CancellationTokenSource _ackToken = null; + private CancellationTokenSource tokenSource = new CancellationTokenSource(); + + public KnxIpSearch() + { + _sendEndPoint = new IPEndPoint(IPAddress.Parse("224.0.23.12"), 3671); + + _sendMessages = new BlockingCollection(); + _receivedAcks = new List(); + + Init(); + + if(OnTunnelResponse != null && OnTunnelRequest != null && OnTunnelAck != null && OnSearchRequest != null) + return; + } + + private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e) + { + _ = SendStatusReq(); + } + + private void Init() + { + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (NetworkInterface adapter in nics) + { + try + { + IPInterfaceProperties ipprops = adapter.GetIPProperties(); + if (ipprops.MulticastAddresses.Count == 0 // most of VPN adapters will be skipped + || !adapter.SupportsMulticast // multicast is meaningless for this type of connection + || OperationalStatus.Up != adapter.OperationalStatus) // this adapter is off or not connected + { + Debug.WriteLine("Skipped " + adapter.Name + " maybe vpn or off"); + continue; + } + + IPv4InterfaceProperties p = ipprops.GetIPv4Properties(); + if (null == p) // IPv4 is not configured on this adapter + { + Debug.WriteLine("Skipped " + adapter.Name + " ip4 not configured"); + continue; + } + + IPAddress addr = ipprops.UnicastAddresses.Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork).Single().Address; + + UdpConnection udp = new UdpConnection(addr, _sendEndPoint); //TODO set source? + udp.InterfaceIndex = IPAddress.HostToNetworkOrder(p.Index); + udp.Interface = adapter; + _clients.Add(udp); + + Debug.WriteLine("Binded to " + adapter.Name + " - " + addr.ToString() + " - " + udp.GetLocalEndpoint().Port + " -> " + udp.InterfaceIndex); + } + catch (Exception ex) + { + Debug.WriteLine("Exception binding to " + adapter.Name); + Debug.WriteLine(ex.Message); + } + } + + Task.Run(ProcessSendMessages, tokenSource.Token); + } + + public Task Send(byte[] data, byte sequence) + { + List xdata = new List(); + + //KNX/IP Header + xdata.Add(0x06); //Header Length + xdata.Add(0x10); //Protokoll Version 1.0 + xdata.Add(0x04); //Service Identifier Family: Tunneling + xdata.Add(0x20); //Service Identifier Type: Request + xdata.AddRange(BitConverter.GetBytes(Convert.ToInt16(data.Length + 10)).Reverse()); //Total length. Set later + + //Connection header + xdata.Add(0x04); // Body Structure Length + xdata.Add(_communicationChannel); // Channel Id + xdata.Add(sequence); // Sequenz Counter + xdata.Add(0x00); // Reserved + xdata.AddRange(data); + + _sendMessages.Add(xdata.ToArray()); + + return Task.CompletedTask; + } + + public Task Send(byte[] data, bool ignoreConnected = false) + { + if (!ignoreConnected && !IsConnected) + throw new Exception("Not connected with interface"); + + _sendMessages.Add(data); + + return Task.CompletedTask; + } + + public Task Send(IMessage message, bool ignoreConnected = false) + { + if (!ignoreConnected && !IsConnected) + throw new Exception("Not connected with interface"); + + byte seq = _sequenceCounter++; + message.SequenceCounter = seq; + _sendMessages.Add(message); + + return Task.FromResult(seq); + } + + public async Task Connect() + { + await Connect(false); + } + + public async Task Connect(bool connectOnly = false) + { + foreach(UdpConnection udp in _clients) + udp.OnReceived += KnxMessageReceived; + } + + public async Task Disconnect() + { + foreach(UdpConnection udp in _clients) + udp.OnReceived -= KnxMessageReceived; + } + + public async Task SendStatusReq() + { + return true; + } + + private void KnxMessageReceived(UdpConnection sender, IParserMessage parserMessage) + { + try + { + switch (parserMessage) + { + case SearchResponse searchResponse: + MsgSearchRes msg = new MsgSearchRes(searchResponse.responseBytes); + switch (CurrentType) + { + case ProtocolTypes.cEmi: + msg.ParseDataCemi(); + break; + case ProtocolTypes.Emi1: + msg.ParseDataEmi1(); + break; + case ProtocolTypes.Emi2: + msg.ParseDataEmi2(); + break; + default: + throw new NotImplementedException("Unbekanntes Protokoll - SearchResponse KnxIpTunneling"); + } + OnSearchResponse?.Invoke(msg, sender.Interface, sender.InterfaceIndex); + break; + + case TunnelAckResponse tunnelAck: + if(tunnelAck.ChannelId != _communicationChannel) return; + _receivedAcks.Add(tunnelAck.SequenceCounter); + if(_ackToken != null) + _ackToken.Cancel(); + break; + + case Kaenx.Konnect.Requests.DisconnectRequest disconnectRequest: + { + if(disconnectRequest.CommunicationChannel != _communicationChannel) return; + IsConnected = false; + _communicationChannel = 0; + ConnectionChanged?.Invoke(IsConnected); + Debug.WriteLine("Die Verbindung wurde vom Gerät geschlossen"); + break; + } + + case DisconnectResponse disconnectResponse: + if(disconnectResponse.CommunicationChannel != _communicationChannel) return; + IsConnected = false; + _communicationChannel = 0; + ConnectionChanged?.Invoke(IsConnected); + break; + } + } + catch (Exception ex) + { + Debug.WriteLine("Exception ProcessSendMessage: " + ex.Message); + } + } + + private async Task ProcessSendMessages() + { + while (!StopProcessing) + { + foreach (var sendMessage in _sendMessages.GetConsumingEnumerable()) + { + if (sendMessage is byte[]) + { + + byte[] data = sendMessage as byte[]; + foreach(UdpConnection udp in _clients) + await udp.SendAsync(data); + } + else if (sendMessage is MsgSearchReq || sendMessage is MsgSearchRes) + { + IMessage message = (IMessage)sendMessage; + + + foreach(UdpConnection udp in _clients) + { + if(message is MsgSearchReq msr) + msr.Endpoint = udp.GetLocalEndpoint(); + + byte[] xdata; + + switch (CurrentType) + { + case ProtocolTypes.Emi1: + xdata = message.GetBytesEmi1(); + break; + + case ProtocolTypes.Emi2: + xdata = message.GetBytesEmi1(); //Todo check diffrences to emi1 + //xdata.AddRange(message.GetBytesEmi2()); + break; + + case ProtocolTypes.cEmi: + xdata = message.GetBytesCemi(); + break; + + default: + throw new Exception("Unbekanntes Protokoll"); + } + await udp.SendAsync(xdata); + } + } + else if (sendMessage is IMessage) + { + IMessage message = sendMessage as IMessage; + message.SourceAddress = UnicastAddress.FromString("0.0.0"); + List xdata = new List + { + //KNX/IP Header + 0x06, //Header Length + 0x10, //Protokoll Version 1.0 + 0x04, //Service Identifier Family: Tunneling + 0x20, //Service Identifier Type: Request + 0x00, //Total length. Set later + 0x00, //Total length. Set later + + //Connection header + 0x04, // Body Structure Length + _communicationChannel, // Channel Id + message.SequenceCounter, // Sequenz Counter + 0x00 //Reserved + }; + + if(_receivedAcks.Contains(message.SequenceCounter)) + _receivedAcks.Remove(message.SequenceCounter); + + switch (CurrentType) + { + case ProtocolTypes.Emi1: + xdata.AddRange(message.GetBytesEmi1()); + break; + + case ProtocolTypes.Emi2: + xdata.AddRange(message.GetBytesEmi1()); //Todo check diffrences between emi1 + //xdata.AddRange(message.GetBytesEmi2()); + break; + + case ProtocolTypes.cEmi: + xdata.AddRange(message.GetBytesCemi()); + break; + + default: + throw new Exception("Unbekanntes Protokoll"); + } + + byte[] length = BitConverter.GetBytes((ushort)xdata.Count); + Array.Reverse(length); + xdata[4] = length[0]; + xdata[5] = length[1]; + + int repeatCounter = 0; + do + { + // if(repeatCounter > 0) + // { + // Console.WriteLine("wiederhole telegrmm " + message.SequenceCounter.ToString()); + // } + if(repeatCounter > 3) + throw new Exception("Zu viele wiederholungen eines Telegramms auf kein OK"); + + foreach(UdpConnection udp in _clients) + await udp.SendAsync(xdata.ToArray()); + + _ackToken = new CancellationTokenSource(); + + try{ + await Task.Delay(1000, _ackToken.Token); + }catch{} + _ackToken = null; + + repeatCounter++; + } while(!_receivedAcks.Contains(message.SequenceCounter)); + } + else + { + throw new Exception("Unbekanntes Element in SendQueue! " + sendMessage.GetType().FullName); + } + } + } + } + + public void Dispose() + { + foreach(UdpConnection udp in _clients) + udp.Dispose(); + tokenSource.Cancel(); + } + } +} \ No newline at end of file diff --git a/Kaenx.Konnect/Connections/KnxIpTunnelConfig.cs b/Kaenx.Konnect/Connections/KnxIpTunnelConfig.cs new file mode 100644 index 0000000..7a311f3 --- /dev/null +++ b/Kaenx.Konnect/Connections/KnxIpTunnelConfig.cs @@ -0,0 +1,502 @@ +using Kaenx.Konnect.Addresses; +using Kaenx.Konnect.Builders; +using Kaenx.Konnect.Classes; +using Kaenx.Konnect.Messages; +using Kaenx.Konnect.Messages.Request; +using Kaenx.Konnect.Messages.Response; +using Kaenx.Konnect.Responses; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using static Kaenx.Konnect.Connections.IKnxConnection; + +namespace Kaenx.Konnect.Connections +{ + internal class KnxIpTunnelingConfig : IKnxConnection + { + public event TunnelRequestHandler OnTunnelRequest; + public event TunnelResponseHandler OnTunnelResponse; + public event TunnelAckHandler OnTunnelAck; + public event ConnectionChangedHandler ConnectionChanged; + + public bool IsConnected { get; set; } + public ConnectionErrors LastError { get; set; } + public UnicastAddress PhysicalAddress { get; set; } + public int MaxFrameLength { get; set; } = 15; + + private ProtocolTypes CurrentType { get; set; } = ProtocolTypes.cEmi; + private byte _communicationChannel; + private bool StopProcessing = false; + private byte _sequenceCounter = 0; + + private readonly IPEndPoint _receiveEndPoint; + private UdpConnection _client; + private readonly BlockingCollection _sendMessages; + + private bool _flagCRRecieved = false; + private List _receivedAcks; + private CancellationTokenSource _ackToken = null; + private CancellationTokenSource tokenSource = new CancellationTokenSource(); + + private System.Timers.Timer _timer = new System.Timers.Timer(60000); + + public KnxIpTunnelingConfig(UdpConnection connection, IPEndPoint receiving) + { + _receiveEndPoint = receiving; + // _sendEndPoint = sending; + _sendMessages = new BlockingCollection(); + _receivedAcks = new List(); + + _client = connection; + + Task.Run(ProcessSendMessages, tokenSource.Token); + + _timer.Elapsed += TimerElapsed; + } + + private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e) + { + _ = SendStatusReq(); + } + + public static int GetFreePort() + { + TcpListener l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + int port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } + + public Task Send(byte[] data, byte sequence) + { + List xdata = new List(); + + //KNX/IP Header + xdata.Add(0x06); //Header Length + xdata.Add(0x10); //Protokoll Version 1.0 + xdata.Add(0x03); //Service Identifier Family: Device Management + xdata.Add(0x10); //Service Identifier Type: ConfigurationRequest + xdata.AddRange(BitConverter.GetBytes(Convert.ToInt16(data.Length + 10)).Reverse()); //Total length. Set later + + //Connection header + xdata.Add(0x04); // Body Structure Length + xdata.Add(_communicationChannel); // Channel Id + xdata.Add(sequence); // Sequenz Counter + xdata.Add(0x00); // Reserved + xdata.AddRange(data); + + _sendMessages.Add(xdata.ToArray()); + + return Task.CompletedTask; + } + + public Task Send(byte[] data, bool ignoreConnected = false) + { + if (!ignoreConnected && !IsConnected) + throw new Exception("Not connected with interface"); + + _sendMessages.Add(data); + + return Task.CompletedTask; + } + + public Task Send(IMessage message, bool ignoreConnected = false) + { + if (!ignoreConnected && !IsConnected) + throw new Exception("Not connected with interface"); + + byte seq = _sequenceCounter++; + message.SequenceCounter = seq; + _sendMessages.Add(message); + + return Task.FromResult(seq); + } + + public async Task Connect() + { + _client.OnReceived += KnxMessageReceived; + _flagCRRecieved = false; + ConnectionRequest builder = new ConnectionRequest(); + builder.Build(_receiveEndPoint, true); + _ackToken = new CancellationTokenSource(); + await _client.SendAsync(builder.GetBytes()); + try{ + await Task.Delay(500, _ackToken.Token); + }catch{} + + if (!_flagCRRecieved) + { + throw new Exception("Schnittstelle ist nicht erreichbar!"); + } + + if (!IsConnected) + { + throw new Exception("Verbindung zur Schnittstelle konnte nicht hergestellt werden! Error: " + LastError); + } + + bool state = await SendStatusReq(); + if (!state) + { + throw new Exception("Die Schnittstelle hat keine Verbindung zum Bus! Error: " + LastError); + } + + _timer.Start(); + } + + public Task Disconnect() + { + _client.OnReceived -= KnxMessageReceived; + if (!IsConnected) + return Task.CompletedTask; + + DisconnectRequest builder = new DisconnectRequest(); + builder.Build(_receiveEndPoint, _communicationChannel); + Send(builder.GetBytes(), true); + + StopProcessing = true; + _timer.Stop(); + return Task.CompletedTask; + } + + public async Task SendStatusReq() + { + ConnectionStatusRequest stat = new ConnectionStatusRequest(); + stat.Build(_receiveEndPoint, _communicationChannel); + stat.SetChannelId(_communicationChannel); + await Send(stat.GetBytes()); + await Task.Delay(200); + return IsConnected; + } + + private void KnxMessageReceived(UdpConnection sender, IParserMessage parserMessage) + { + try + { + switch (parserMessage) + { + case ConnectStateResponse connectStateResponse: + //Debug.WriteLine("Connection State Response: " + connectStateResponse.Status.ToString()); + switch (connectStateResponse.Status) + { + case 0x00: + IsConnected = true; + ConnectionChanged?.Invoke(IsConnected); + break; + default: + //Debug.WriteLine("Connection State: Fehler: " + connectStateResponse.Status.ToString()); + LastError = ConnectionErrors.NotConnectedToBus; + IsConnected = false; + ConnectionChanged?.Invoke(IsConnected); + break; + } + break; + + case ConnectResponse connectResponse: + _flagCRRecieved = true; + switch (connectResponse.Status) + { + case 0x00: + _sequenceCounter = 0; + _communicationChannel = connectResponse.CommunicationChannel; + IsConnected = true; + ConnectionChanged?.Invoke(IsConnected); + PhysicalAddress = connectResponse.ConnectionResponseDataBlock.KnxAddress; + //Debug.WriteLine("Connected: Eigene Adresse: " + PhysicalAddress.ToString()); + break; + default: + //Debug.WriteLine("Connected: Fehler: " + connectResponse.Status.ToString()); + LastError = ConnectionErrors.Undefined; + IsConnected = false; + ConnectionChanged?.Invoke(IsConnected); + break; + } + //if(_ackToken != null) + // _ackToken.Cancel(); + break; + + case Requests.TunnelRequest tunnelResponse: + if (tunnelResponse.APCI.ToString().EndsWith("Request") && tunnelResponse.DestinationAddress != PhysicalAddress) + { + //Debug.WriteLine("Telegram erhalten das nicht mit der Adresse selbst zu tun hat!"); + //Debug.WriteLine("Typ: " + tunnelResponse.APCI); + //Debug.WriteLine("Eigene Adresse: " + PhysicalAddress.ToString()); + break; + } + + _sendMessages.Add(new Responses.TunnelResponse(0x06, 0x10, 0x0A, 0x04, _communicationChannel, tunnelResponse.SequenceCounter, 0x00).GetBytes()); + + //Debug.WriteLine("Telegram APCI: " + tunnelResponse.APCI.ToString()); + + if (tunnelResponse.IsNumbered && tunnelResponse.APCI.ToString().EndsWith("Response")) + { + Messages.Response.MsgAckRes msgAckRes = new MsgAckRes + { + DestinationAddress = tunnelResponse.SourceAddress, + SequenceNumber = tunnelResponse.SequenceNumber + }; + _ = Send(msgAckRes); + } + else if (tunnelResponse.APCI == ApciTypes.Ack) + { + OnTunnelAck?.Invoke(new MsgAckRes() + { + ChannelId = tunnelResponse.CommunicationChannel, + SequenceCounter = tunnelResponse.SequenceCounter, + SequenceNumber = tunnelResponse.SequenceNumber, + SourceAddress = tunnelResponse.SourceAddress, + DestinationAddress = tunnelResponse.DestinationAddress + }); + break; + } + + var q = from t in Assembly.GetExecutingAssembly().GetTypes() + where t.IsClass && t.IsNested == false && (t.Namespace == "Kaenx.Konnect.Messages.Response" || t.Namespace == "Kaenx.Konnect.Messages.Request") + select t; + + IMessage message = null; + + foreach (Type t in q.ToList()) + { + IMessage resp = (IMessage)Activator.CreateInstance(t); + + if (resp.ApciType == tunnelResponse.APCI) + { + message = resp; + break; + } + } + + if (message == null) + { + //throw new Exception("Kein MessageParser für den APCI " + tunnelResponse.APCI); + if (tunnelResponse.APCI.ToString().EndsWith("Response")) + { + message = new MsgDefaultRes(tunnelResponse.IsNumbered) + { + ApciType = tunnelResponse.APCI + }; + } else { + message = new MsgDefaultReq(tunnelResponse.IsNumbered) + { + ApciType = tunnelResponse.APCI + }; + } + //Debug.WriteLine("Kein MessageParser für den APCI " + tunnelResponse.APCI); + } + + message.Raw = tunnelResponse.Data; + message.ChannelId = tunnelResponse.CommunicationChannel; + message.SequenceCounter = tunnelResponse.SequenceCounter; + message.SequenceNumber = tunnelResponse.SequenceNumber; + message.SourceAddress = tunnelResponse.SourceAddress; + message.DestinationAddress = tunnelResponse.DestinationAddress; + + switch (CurrentType) + { + case ProtocolTypes.cEmi: + message.ParseDataCemi(); + break; + case ProtocolTypes.Emi1: + message.ParseDataEmi1(); + break; + case ProtocolTypes.Emi2: + message.ParseDataEmi2(); + break; + default: + throw new NotImplementedException("Unbekanntes Protokoll - TunnelResponse KnxIpTunneling"); + } + + + if (tunnelResponse.APCI.ToString().EndsWith("Response")) + OnTunnelResponse?.Invoke(message as IMessageResponse); + else + OnTunnelRequest?.Invoke(message as IMessageRequest); + + break; + + //TODO SearchRequest? + case SearchResponse searchResponse: + MsgSearchRes msg = new MsgSearchRes(searchResponse.responseBytes); + switch (CurrentType) + { + case ProtocolTypes.cEmi: + msg.ParseDataCemi(); + break; + case ProtocolTypes.Emi1: + msg.ParseDataEmi1(); + break; + case ProtocolTypes.Emi2: + msg.ParseDataEmi2(); + break; + default: + throw new NotImplementedException("Unbekanntes Protokoll - SearchResponse KnxIpTunneling"); + } + OnTunnelResponse?.Invoke(msg); + break; + + case TunnelAckResponse tunnelAck: + _receivedAcks.Add(tunnelAck.SequenceCounter); + if(_ackToken != null) + _ackToken.Cancel(); + break; + + case Kaenx.Konnect.Requests.DisconnectRequest disconnectRequest: + { + if(disconnectRequest.CommunicationChannel != _communicationChannel) return; + IsConnected = false; + _communicationChannel = 0; + ConnectionChanged?.Invoke(IsConnected); + Debug.WriteLine("Die Verbindung wurde vom Gerät geschlossen"); + break; + } + + case DisconnectResponse disconnectResponse: + IsConnected = false; + _communicationChannel = 0; + ConnectionChanged?.Invoke(IsConnected); + break; + } + } + catch (Exception ex) + { + Debug.WriteLine("Exception ProcessSendMessage: " + ex.Message); + } + } + + private async Task ProcessSendMessages() + { + while (!StopProcessing) + { + foreach (var sendMessage in _sendMessages.GetConsumingEnumerable()) + { + if (sendMessage is byte[]) + { + + byte[] data = sendMessage as byte[]; + await _client.SendAsync(data); + } + else if (sendMessage is MsgSearchReq || sendMessage is MsgSearchRes) + { + IMessage message = (IMessage)sendMessage; + if(message is MsgSearchReq msr) + msr.Endpoint = _receiveEndPoint; + + byte[] xdata; + + switch (CurrentType) + { + case ProtocolTypes.Emi1: + xdata = message.GetBytesEmi1(); + break; + + case ProtocolTypes.Emi2: + xdata = message.GetBytesEmi1(); //Todo check diffrences to emi1 + //xdata.AddRange(message.GetBytesEmi2()); + break; + + case ProtocolTypes.cEmi: + xdata = message.GetBytesCemi(); + break; + + default: + throw new Exception("Unbekanntes Protokoll"); + } + + await _client.SendAsync(xdata); + } + else if (sendMessage is IMessage) + { + IMessage message = sendMessage as IMessage; + message.SourceAddress = UnicastAddress.FromString("0.0.0"); + List xdata = new List + { + //KNX/IP Header + 0x06, //Header Length + 0x10, //Protokoll Version 1.0 + 0x04, //Service Identifier Family: Tunneling + 0x20, //Service Identifier Type: Request + 0x00, //Total length. Set later + 0x00, //Total length. Set later + + //Connection header + 0x04, // Body Structure Length + _communicationChannel, // Channel Id + message.SequenceCounter, // Sequenz Counter + 0x00 //Reserved + }; + + if(_receivedAcks.Contains(message.SequenceCounter)) + _receivedAcks.Remove(message.SequenceCounter); + + switch (CurrentType) + { + case ProtocolTypes.Emi1: + xdata.AddRange(message.GetBytesEmi1()); + break; + + case ProtocolTypes.Emi2: + xdata.AddRange(message.GetBytesEmi1()); //Todo check diffrences between emi1 + //xdata.AddRange(message.GetBytesEmi2()); + break; + + case ProtocolTypes.cEmi: + xdata.AddRange(message.GetBytesCemi()); + break; + + default: + throw new Exception("Unbekanntes Protokoll"); + } + + byte[] length = BitConverter.GetBytes((ushort)xdata.Count); + Array.Reverse(length); + xdata[4] = length[0]; + xdata[5] = length[1]; + + int repeatCounter = 0; + do + { + // if(repeatCounter > 0) + // { + // Console.WriteLine("wiederhole telegrmm " + message.SequenceCounter.ToString()); + // } + if(repeatCounter > 3) + throw new Exception("Zu viele wiederholungen eines Telegramms auf kein OK"); + + await _client.SendAsync(xdata.ToArray()); + + _ackToken = new CancellationTokenSource(); + + try{ + await Task.Delay(1000, _ackToken.Token); + }catch{} + _ackToken = null; + + repeatCounter++; + } while(!_receivedAcks.Contains(message.SequenceCounter)); + } + else + { + throw new Exception("Unbekanntes Element in SendQueue! " + sendMessage.GetType().FullName); + } + } + } + } + + public void Dispose() + { + if(_client != null) + _client.Dispose(); + tokenSource.Cancel(); + } + } +} \ No newline at end of file diff --git a/Kaenx.Konnect/Connections/KnxIpTunneling.cs b/Kaenx.Konnect/Connections/KnxIpTunneling.cs index 3da51a2..7de5bf6 100644 --- a/Kaenx.Konnect/Connections/KnxIpTunneling.cs +++ b/Kaenx.Konnect/Connections/KnxIpTunneling.cs @@ -11,9 +11,11 @@ using System.Diagnostics; using System.Linq; using System.Net; +using System.Net.Http; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; +using System.Reflection.Metadata.Ecma335; using System.Threading; using System.Threading.Tasks; using static Kaenx.Konnect.Connections.IKnxConnection; @@ -25,34 +27,32 @@ public class KnxIpTunneling : IKnxConnection public event TunnelRequestHandler OnTunnelRequest; public event TunnelResponseHandler OnTunnelResponse; public event TunnelAckHandler OnTunnelAck; - public event SearchResponseHandler OnSearchResponse; - public event SearchRequestHandler OnSearchRequest; public event ConnectionChangedHandler ConnectionChanged; - public int Port; public bool IsConnected { get; set; } public ConnectionErrors LastError { get; set; } public UnicastAddress PhysicalAddress { get; set; } + public int MaxFrameLength { get; set; } = 15; private ProtocolTypes CurrentType { get; set; } = ProtocolTypes.cEmi; private byte _communicationChannel; - private bool StopProcessing = false; private byte _sequenceCounter = 0; private readonly IPEndPoint _receiveEndPoint; private readonly IPEndPoint _sendEndPoint; - private List _udpList = new List(); - private readonly BlockingCollection _sendMessages; - private readonly ReceiverParserDispatcher _receiveParserDispatcher; + private UdpConnection _client; + private readonly Queue _sendMessages; + private bool _flagCRRecieved = false; private List _receivedAcks; private CancellationTokenSource _ackToken = null; + private CancellationTokenSource tokenSource; private System.Timers.Timer _timer = new System.Timers.Timer(60000); + private bool isInConfig = false; - public KnxIpTunneling(string ip, int port, bool sendBroadcast = false) + public KnxIpTunneling(string ip, int port) { - Port = GetFreePort(); _sendEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); IPAddress IP = GetIpAddress(ip); @@ -60,29 +60,26 @@ public KnxIpTunneling(string ip, int port, bool sendBroadcast = false) if (IP == null) throw new Exception("Lokale Ip konnte nicht gefunden werden"); - _receiveEndPoint = new IPEndPoint(IP, Port); - _receiveParserDispatcher = new ReceiverParserDispatcher(); - _sendMessages = new BlockingCollection(); + _receiveEndPoint = new IPEndPoint(IP, 0); + _sendMessages = new Queue(); _receivedAcks = new List(); - Init(sendBroadcast); + Init(); _timer.Elapsed += TimerElapsed; } - public KnxIpTunneling(IPEndPoint sendEndPoint, bool sendBroadcast = false) + public KnxIpTunneling(IPEndPoint sendEndPoint) { - Port = GetFreePort(); _sendEndPoint = sendEndPoint; IPAddress ip = GetIpAddress(sendEndPoint.Address.ToString()); if (ip == null) throw new Exception("Lokale Ip konnte nicht gefunden werden"); - _receiveEndPoint = new IPEndPoint(ip, Port); - _receiveParserDispatcher = new ReceiverParserDispatcher(); - _sendMessages = new BlockingCollection(); + _receiveEndPoint = new IPEndPoint(ip, 0); + _sendMessages = new Queue(); - Init(sendBroadcast); + Init(); _timer.Elapsed += TimerElapsed; } @@ -147,66 +144,9 @@ private IPAddress GetIpAddress(string receiver) return IP; } - private void Init(bool sendBroadcast = false) + private void Init() { - if (sendBroadcast) - { - NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); - foreach (NetworkInterface adapter in nics) - { - //TODO try this - /* - * udpClient = new UdpClient(8088); - udpClient.JoinMulticastGroup(IPAddress.Parse("224.100.0.1"), 50); - - */ - try - { - IPInterfaceProperties ipprops = adapter.GetIPProperties(); - /*if (ipprops.MulticastAddresses.Count == 0 // most of VPN adapters will be skipped - || !adapter.SupportsMulticast // multicast is meaningless for this type of connection - || OperationalStatus.Up != adapter.OperationalStatus) // this adapter is off or not connected - continue;*/ - IPv4InterfaceProperties p = ipprops.GetIPv4Properties(); - if (p == null) continue; // IPv4 is not configured on this adapter - int index = IPAddress.HostToNetworkOrder(p.Index); - - IPAddress addr = adapter.GetIPProperties().UnicastAddresses.Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork).Single().Address; - UdpClient _udpClient = new UdpClient(); - _udpClient.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, index); - _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - _udpClient.Client.Bind(new IPEndPoint(addr, GetFreePort())); - _udpList.Add(_udpClient); - - //Debug.WriteLine("Binded to " + adapter.Name); - } - catch (Exception ex) - { - Debug.WriteLine("Exception binding to " + adapter.Name); - Debug.WriteLine(ex.Message); - } - } - } - else - { - UdpClient _udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, Port)); - _udpList.Add(_udpClient); - //Debug.WriteLine("Binded to default"); - } - - ProcessSendMessages(); - - foreach (UdpClient client in _udpList) - ProcessReceivingMessages(client); - } - - public static int GetFreePort() - { - TcpListener l = new TcpListener(IPAddress.Loopback, 0); - l.Start(); - int port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; + _client = new UdpConnection(_receiveEndPoint.Address, _receiveEndPoint.Port, _sendEndPoint); } public Task Send(byte[] data, byte sequence) @@ -227,7 +167,7 @@ public Task Send(byte[] data, byte sequence) xdata.Add(0x00); // Reserved xdata.AddRange(data); - _sendMessages.Add(xdata.ToArray()); + _sendMessages.Enqueue(xdata.ToArray()); return Task.CompletedTask; } @@ -237,7 +177,7 @@ public Task Send(byte[] data, bool ignoreConnected = false) if (!ignoreConnected && !IsConnected) throw new Exception("Not connected with interface"); - _sendMessages.Add(data); + _sendMessages.Enqueue(data); return Task.CompletedTask; } @@ -249,20 +189,26 @@ public Task Send(IMessage message, bool ignoreConnected = false) byte seq = _sequenceCounter++; message.SequenceCounter = seq; - _sendMessages.Add(message); + _sendMessages.Enqueue(message); return Task.FromResult(seq); } public async Task Connect() { + await Connect(false); + } + + public async Task Connect(bool connectOnly = false) + { + _client.OnReceived += KnxMessageReceived; _flagCRRecieved = false; ConnectionRequest builder = new ConnectionRequest(); - builder.Build(_receiveEndPoint, 0x00); + builder.Build(_receiveEndPoint); _ackToken = new CancellationTokenSource(); - await Send(builder.GetBytes(), true); + await _client.SendAsync(builder.GetBytes()); try{ - await Task.Delay(500, _ackToken.Token); + await Task.Delay(500, _ackToken.Token); }catch{} if (!_flagCRRecieved) @@ -274,18 +220,41 @@ public async Task Connect() { throw new Exception("Verbindung zur Schnittstelle konnte nicht hergestellt werden! Error: " + LastError); } - - bool state = await SendStatusReq(); - if (!state) + + // bool state = await SendStatusReq(); + // if (!state) + // { + // throw new Exception("Die Schnittstelle hat keine Verbindung zum Bus! Error: " + LastError); + // } + + if(!connectOnly) { - throw new Exception("Die Schnittstelle hat keine Verbindung zum Bus! Error: " + LastError); + _client.OnReceived -= KnxMessageReceived; + + KnxIpTunnelingConfig conf = new KnxIpTunnelingConfig(_client, _receiveEndPoint); + + try{ + isInConfig = true; + await conf.Connect(); + } catch { + //do nothing? + } finally { + await conf.Disconnect(); + isInConfig = false; + } + + _client.OnReceived += KnxMessageReceived; } + tokenSource = new CancellationTokenSource(); + _ = Task.Run(ProcessSendMessages, tokenSource.Token); + _timer.Start(); } public Task Disconnect() { + _client.OnReceived -= KnxMessageReceived; if (!IsConnected) return Task.CompletedTask; @@ -293,7 +262,13 @@ public Task Disconnect() builder.Build(_receiveEndPoint, _communicationChannel); Send(builder.GetBytes(), true); - StopProcessing = true; + tokenSource.Cancel(); + + if(isInConfig) + { + // Really disconnect? + } + _timer.Stop(); return Task.CompletedTask; } @@ -308,342 +283,353 @@ public async Task SendStatusReq() return IsConnected; } - private void ProcessReceivingMessages(UdpClient _udpClient) + private void KnxMessageReceived(UdpConnection sender, IParserMessage parserMessage) { - //Debug.WriteLine("Höre jetzt auf: " + (_udpClient.Client.LocalEndPoint as IPEndPoint).Port); - Task.Run(async () => + try { - int rofl = 0; - while (!StopProcessing) + switch (parserMessage) { - try - { - rofl++; - var result = await _udpClient.ReceiveAsync(); - var knxResponse = _receiveParserDispatcher.Build(result.Buffer); - - //if(!(knxResponse is SearchResponse)) - // Debug.WriteLine("Telegram angekommen: " + knxResponse?.ToString()); - - switch (knxResponse) + case ConnectStateResponse connectStateResponse: + if(connectStateResponse.CommunicationChannel != _communicationChannel) return; + //Debug.WriteLine("Connection State Response: " + connectStateResponse.Status.ToString()); + switch (connectStateResponse.Status) { - case ConnectStateResponse connectStateResponse: - //Debug.WriteLine("Connection State Response: " + connectStateResponse.Status.ToString()); - switch (connectStateResponse.Status) - { - case 0x00: - IsConnected = true; - ConnectionChanged?.Invoke(IsConnected); - break; - default: - //Debug.WriteLine("Connection State: Fehler: " + connectStateResponse.Status.ToString()); - LastError = ConnectionErrors.NotConnectedToBus; - IsConnected = false; - ConnectionChanged?.Invoke(IsConnected); - break; - } + case 0x00: + IsConnected = true; + ConnectionChanged?.Invoke(IsConnected); break; + default: + //Debug.WriteLine("Connection State: Fehler: " + connectStateResponse.Status.ToString()); + LastError = ConnectionErrors.NotConnectedToBus; + IsConnected = false; + ConnectionChanged?.Invoke(IsConnected); + break; + } + break; - case ConnectResponse connectResponse: - _flagCRRecieved = true; - switch (connectResponse.Status) - { - case 0x00: - _sequenceCounter = 0; - _communicationChannel = connectResponse.CommunicationChannel; - IsConnected = true; - ConnectionChanged?.Invoke(IsConnected); - PhysicalAddress = connectResponse.ConnectionResponseDataBlock.KnxAddress; - //Debug.WriteLine("Connected: Eigene Adresse: " + PhysicalAddress.ToString()); - break; - default: - //Debug.WriteLine("Connected: Fehler: " + connectResponse.Status.ToString()); - LastError = ConnectionErrors.Undefined; - IsConnected = false; - ConnectionChanged?.Invoke(IsConnected); - break; - } - //if(_ackToken != null) - // _ackToken.Cancel(); + case ConnectResponse connectResponse: + _flagCRRecieved = true; + switch (connectResponse.Status) + { + case 0x00: + _sequenceCounter = 0; + _communicationChannel = connectResponse.CommunicationChannel; + IsConnected = true; + ConnectionChanged?.Invoke(IsConnected); + PhysicalAddress = connectResponse.ConnectionResponseDataBlock.KnxAddress; + //Debug.WriteLine("Connected: Eigene Adresse: " + PhysicalAddress.ToString()); break; + default: + //Debug.WriteLine("Connected: Fehler: " + connectResponse.Status.ToString()); + LastError = ConnectionErrors.Undefined; + IsConnected = false; + ConnectionChanged?.Invoke(IsConnected); + break; + } + //if(_ackToken != null) + // _ackToken.Cancel(); + break; - case Requests.TunnelRequest tunnelResponse: - if (tunnelResponse.APCI.ToString().EndsWith("Request") && tunnelResponse.DestinationAddress != PhysicalAddress) - { - //Debug.WriteLine("Telegram erhalten das nicht mit der Adresse selbst zu tun hat!"); - //Debug.WriteLine("Typ: " + tunnelResponse.APCI); - //Debug.WriteLine("Eigene Adresse: " + PhysicalAddress.ToString()); - break; - } + case Requests.TunnelRequest tunnelResponse: + if(tunnelResponse.CommunicationChannel != _communicationChannel) return; + if (tunnelResponse.APCI.ToString().EndsWith("Request") && tunnelResponse.DestinationAddress != PhysicalAddress) + { + //Debug.WriteLine("Telegram erhalten das nicht mit der Adresse selbst zu tun hat!"); + //Debug.WriteLine("Typ: " + tunnelResponse.APCI); + //Debug.WriteLine("Eigene Adresse: " + PhysicalAddress.ToString()); + break; + } - _sendMessages.Add(new Responses.TunnelResponse(0x06, 0x10, 0x0A, 0x04, _communicationChannel, tunnelResponse.SequenceCounter, 0x00).GetBytes()); + _sendMessages.Enqueue(new Responses.TunnelResponse(0x06, 0x10, 0x0A, 0x04, _communicationChannel, tunnelResponse.SequenceCounter, 0x00).GetBytes()); - //Debug.WriteLine("Telegram APCI: " + tunnelResponse.APCI.ToString()); + //Debug.WriteLine("Telegram APCI: " + tunnelResponse.APCI.ToString()); - if (tunnelResponse.IsNumbered && tunnelResponse.APCI.ToString().EndsWith("Response")) - { - Messages.Response.MsgAckRes msgAckRes = new MsgAckRes(); - msgAckRes.DestinationAddress = tunnelResponse.SourceAddress; - msgAckRes.SequenceNumber = tunnelResponse.SequenceNumber; - _ = Send(msgAckRes); - } - else if (tunnelResponse.APCI == ApciTypes.Ack) - { - OnTunnelAck?.Invoke(new MsgAckRes() - { - ChannelId = tunnelResponse.CommunicationChannel, - SequenceCounter = tunnelResponse.SequenceCounter, - SequenceNumber = tunnelResponse.SequenceNumber, - SourceAddress = tunnelResponse.SourceAddress, - DestinationAddress = tunnelResponse.DestinationAddress - }); - break; - } - - var q = from t in Assembly.GetExecutingAssembly().GetTypes() - where t.IsClass && t.IsNested == false && (t.Namespace == "Kaenx.Konnect.Messages.Response" || t.Namespace == "Kaenx.Konnect.Messages.Request") - select t; - - IMessage message = null; - - foreach (Type t in q.ToList()) - { - IMessage resp = (IMessage)Activator.CreateInstance(t); + if (tunnelResponse.IsNumbered && tunnelResponse.APCI.ToString().EndsWith("Response")) + { + Messages.Response.MsgAckRes msgAckRes = new MsgAckRes + { + DestinationAddress = tunnelResponse.SourceAddress, + SequenceNumber = tunnelResponse.SequenceNumber + }; + _ = Send(msgAckRes); + } + else if (tunnelResponse.APCI == ApciTypes.Ack) + { + OnTunnelAck?.Invoke(new MsgAckRes() + { + ChannelId = tunnelResponse.CommunicationChannel, + SequenceCounter = tunnelResponse.SequenceCounter, + SequenceNumber = tunnelResponse.SequenceNumber, + SourceAddress = tunnelResponse.SourceAddress, + DestinationAddress = tunnelResponse.DestinationAddress + }); + break; + } - if (resp.ApciType == tunnelResponse.APCI) - { - message = resp; - break; - } - } + var q = from t in Assembly.GetExecutingAssembly().GetTypes() + where t.IsClass && t.IsNested == false && (t.Namespace == "Kaenx.Konnect.Messages.Response" || t.Namespace == "Kaenx.Konnect.Messages.Request") + select t; - if (message == null) - { - //throw new Exception("Kein MessageParser für den APCI " + tunnelResponse.APCI); - if (tunnelResponse.APCI.ToString().EndsWith("Response")) - { - message = new MsgDefaultRes(tunnelResponse.IsNumbered) - { - ApciType = tunnelResponse.APCI - }; - } else { - message = new MsgDefaultReq(tunnelResponse.IsNumbered) - { - ApciType = tunnelResponse.APCI - }; - } - //Debug.WriteLine("Kein MessageParser für den APCI " + tunnelResponse.APCI); - } - - message.Raw = tunnelResponse.Data; - message.ChannelId = tunnelResponse.CommunicationChannel; - message.SequenceCounter = tunnelResponse.SequenceCounter; - message.SequenceNumber = tunnelResponse.SequenceNumber; - message.SourceAddress = tunnelResponse.SourceAddress; - message.DestinationAddress = tunnelResponse.DestinationAddress; - - switch (CurrentType) - { - case ProtocolTypes.cEmi: - message.ParseDataCemi(); - break; - case ProtocolTypes.Emi1: - message.ParseDataEmi1(); - break; - case ProtocolTypes.Emi2: - message.ParseDataEmi2(); - break; - default: - throw new NotImplementedException("Unbekanntes Protokoll - TunnelResponse KnxIpTunneling"); - } - - - if (tunnelResponse.APCI.ToString().EndsWith("Response")) - OnTunnelResponse?.Invoke(message as IMessageResponse); - else - OnTunnelRequest?.Invoke(message as IMessageRequest); + IMessage message = null; + + foreach (Type t in q.ToList()) + { + IMessage resp = (IMessage)Activator.CreateInstance(t); + if (resp.ApciType == tunnelResponse.APCI) + { + message = resp; break; + } + } - case SearchResponse searchResponse: - MsgSearchRes msg = new MsgSearchRes(searchResponse.responseBytes); - switch (CurrentType) + if (message == null) + { + //throw new Exception("Kein MessageParser für den APCI " + tunnelResponse.APCI); + if (tunnelResponse.APCI.ToString().EndsWith("Response")) + { + message = new MsgDefaultRes(tunnelResponse.IsNumbered) { - case ProtocolTypes.cEmi: - msg.ParseDataCemi(); - break; - case ProtocolTypes.Emi1: - msg.ParseDataEmi1(); - break; - case ProtocolTypes.Emi2: - msg.ParseDataEmi2(); - break; - default: - throw new NotImplementedException("Unbekanntes Protokoll - SearchResponse KnxIpTunneling"); - } - OnSearchResponse?.Invoke(msg); - break; + ApciType = tunnelResponse.APCI + }; + } else { + message = new MsgDefaultReq(tunnelResponse.IsNumbered) + { + ApciType = tunnelResponse.APCI + }; + } + //Debug.WriteLine("Kein MessageParser für den APCI " + tunnelResponse.APCI); + } - case TunnelAckResponse tunnelAck: - _receivedAcks.Add(tunnelAck.SequenceCounter); - if(_ackToken != null) - _ackToken.Cancel(); + message.Raw = tunnelResponse.Data; + message.ChannelId = tunnelResponse.CommunicationChannel; + message.SequenceCounter = tunnelResponse.SequenceCounter; + message.SequenceNumber = tunnelResponse.SequenceNumber; + message.SourceAddress = tunnelResponse.SourceAddress; + message.DestinationAddress = tunnelResponse.DestinationAddress; + + switch (CurrentType) + { + case ProtocolTypes.cEmi: + message.ParseDataCemi(); + break; + case ProtocolTypes.Emi1: + message.ParseDataEmi1(); + break; + case ProtocolTypes.Emi2: + message.ParseDataEmi2(); break; + default: + throw new NotImplementedException("Unbekanntes Protokoll - TunnelResponse KnxIpTunneling"); + } - case Kaenx.Konnect.Requests.DisconnectRequest disconnectRequest: - { - if(disconnectRequest.CommunicationChannel != _communicationChannel) return; - IsConnected = false; - _communicationChannel = 0; - ConnectionChanged?.Invoke(IsConnected); - Debug.WriteLine("Die Verbindung wurde vom Gerät geschlossen"); + + if (tunnelResponse.APCI.ToString().EndsWith("Response")) + OnTunnelResponse?.Invoke(message as IMessageResponse); + else + OnTunnelRequest?.Invoke(message as IMessageRequest); + + break; + + case Requests.SearchRequest searchRequest: + { + MsgSearchReq msg = new MsgSearchReq(searchRequest.responseBytes); + switch (CurrentType) + { + case ProtocolTypes.cEmi: + msg.ParseDataCemi(); break; - } + case ProtocolTypes.Emi1: + msg.ParseDataEmi1(); + break; + case ProtocolTypes.Emi2: + msg.ParseDataEmi2(); + break; + default: + throw new NotImplementedException("Unbekanntes Protokoll - SearchResponse KnxIpTunneling"); + } + OnTunnelRequest?.Invoke(msg); + break; + } - case DisconnectResponse disconnectResponse: - IsConnected = false; - _communicationChannel = 0; - ConnectionChanged?.Invoke(IsConnected); + case SearchResponse searchResponse: + { + MsgSearchRes msg = new MsgSearchRes(searchResponse.responseBytes); + switch (CurrentType) + { + case ProtocolTypes.cEmi: + msg.ParseDataCemi(); + break; + case ProtocolTypes.Emi1: + msg.ParseDataEmi1(); break; + case ProtocolTypes.Emi2: + msg.ParseDataEmi2(); + break; + default: + throw new NotImplementedException("Unbekanntes Protokoll - SearchResponse KnxIpTunneling"); } + OnTunnelResponse?.Invoke(msg); + break; } - catch (Exception ex) + + case TunnelAckResponse tunnelAck: + if(tunnelAck.ChannelId != _communicationChannel) return; + _receivedAcks.Add(tunnelAck.SequenceCounter); + if(_ackToken != null) + _ackToken.Cancel(); + break; + + case Kaenx.Konnect.Requests.DisconnectRequest disconnectRequest: { - Debug.WriteLine("Exception ProcessSendMessage: " + ex.Message); + if(disconnectRequest.CommunicationChannel != _communicationChannel) return; + IsConnected = false; + _communicationChannel = 0; + ConnectionChanged?.Invoke(IsConnected); + Debug.WriteLine("Die Verbindung wurde vom Gerät geschlossen"); + break; } - } - //Debug.WriteLine("Stopped Processing Messages " + _udpClient.Client.LocalEndPoint.ToString()); - _udpClient.Close(); - _udpClient.Dispose(); - }); + case DisconnectResponse disconnectResponse: + if(disconnectResponse.CommunicationChannel != _communicationChannel) return; + IsConnected = false; + _communicationChannel = 0; + ConnectionChanged?.Invoke(IsConnected); + break; + } + } + catch (Exception ex) + { + Debug.WriteLine("Exception ProcessSendMessage: " + ex.Message); + } } - private void ProcessSendMessages() + private async Task ProcessSendMessages() { - Task.Run(async () => + while (!tokenSource.IsCancellationRequested) { - foreach (var sendMessage in _sendMessages.GetConsumingEnumerable()) + if(_sendMessages.Count == 0) + continue; + + var sendMessage = _sendMessages.Dequeue(); + + if (sendMessage is byte[]) { - if (sendMessage is byte[]) - { - - byte[] data = sendMessage as byte[]; - foreach (UdpClient client in _udpList) - { - await client.SendAsync(data, data.Length, _sendEndPoint); - } - - } - else if (sendMessage is MsgSearchReq || sendMessage is MsgSearchRes) - { - IMessage message = (IMessage)sendMessage; - foreach (UdpClient client in _udpList) - { - if(message is MsgSearchReq msr) - msr.Endpoint = client.Client.LocalEndPoint as IPEndPoint; - byte[] xdata; + byte[] data = sendMessage as byte[]; + await _client.SendAsync(data); + } + else if (sendMessage is MsgSearchReq || sendMessage is MsgSearchRes) + { + IMessage message = (IMessage)sendMessage; + if(message is MsgSearchReq msr) + msr.Endpoint = _receiveEndPoint; - switch (CurrentType) - { - case ProtocolTypes.Emi1: - xdata = message.GetBytesEmi1(); - break; + byte[] xdata; - case ProtocolTypes.Emi2: - xdata = message.GetBytesEmi1(); //Todo check diffrences to emi1 - //xdata.AddRange(message.GetBytesEmi2()); - break; + switch (CurrentType) + { + case ProtocolTypes.Emi1: + xdata = message.GetBytesEmi1(); + break; - case ProtocolTypes.cEmi: - xdata = message.GetBytesCemi(); - break; + case ProtocolTypes.Emi2: + xdata = message.GetBytesEmi1(); //Todo check diffrences to emi1 + //xdata.AddRange(message.GetBytesEmi2()); + break; - default: - throw new Exception("Unbekanntes Protokoll"); - } + case ProtocolTypes.cEmi: + xdata = message.GetBytesCemi(); + break; - await client.SendAsync(xdata, xdata.Length, _sendEndPoint); - } + default: + throw new Exception("Unbekanntes Protokoll"); } - else if (sendMessage is IMessage) - { - IMessage message = sendMessage as IMessage; - message.SourceAddress = UnicastAddress.FromString("0.0.0"); - List xdata = new List - { - //KNX/IP Header - 0x06, //Header Length - 0x10, //Protokoll Version 1.0 - 0x04, //Service Identifier Family: Tunneling - 0x20, //Service Identifier Type: Request - 0x00, //Total length. Set later - 0x00, //Total length. Set later - - //Connection header - 0x04, // Body Structure Length - _communicationChannel, // Channel Id - message.SequenceCounter, // Sequenz Counter - 0x00 //Reserved - }; - - if(_receivedAcks.Contains(message.SequenceCounter)) - _receivedAcks.Remove(message.SequenceCounter); - switch (CurrentType) - { - case ProtocolTypes.Emi1: - xdata.AddRange(message.GetBytesEmi1()); - break; + await _client.SendAsync(xdata); + } + else if (sendMessage is IMessage) + { + IMessage message = sendMessage as IMessage; + message.SourceAddress = UnicastAddress.FromString("0.0.0"); + List xdata = new List + { + //KNX/IP Header + 0x06, //Header Length + 0x10, //Protokoll Version 1.0 + 0x04, //Service Identifier Family: Tunneling + 0x20, //Service Identifier Type: Request + 0x00, //Total length. Set later + 0x00, //Total length. Set later + + //Connection header + 0x04, // Body Structure Length + _communicationChannel, // Channel Id + message.SequenceCounter, // Sequenz Counter + 0x00 //Reserved + }; + + if(_receivedAcks.Contains(message.SequenceCounter)) + _receivedAcks.Remove(message.SequenceCounter); + + switch (CurrentType) + { + case ProtocolTypes.Emi1: + xdata.AddRange(message.GetBytesEmi1()); + break; - case ProtocolTypes.Emi2: - xdata.AddRange(message.GetBytesEmi1()); //Todo check diffrences between emi1 - //xdata.AddRange(message.GetBytesEmi2()); - break; + case ProtocolTypes.Emi2: + xdata.AddRange(message.GetBytesEmi1()); //Todo check diffrences between emi1 + //xdata.AddRange(message.GetBytesEmi2()); + break; - case ProtocolTypes.cEmi: - xdata.AddRange(message.GetBytesCemi()); - break; + case ProtocolTypes.cEmi: + xdata.AddRange(message.GetBytesCemi()); + break; - default: - throw new Exception("Unbekanntes Protokoll"); - } + default: + throw new Exception("Unbekanntes Protokoll"); + } - byte[] length = BitConverter.GetBytes((ushort)(xdata.Count)); - Array.Reverse(length); - xdata[4] = length[0]; - xdata[5] = length[1]; + byte[] length = BitConverter.GetBytes((ushort)xdata.Count); + Array.Reverse(length); + xdata[4] = length[0]; + xdata[5] = length[1]; - int repeatCounter = 0; - do - { - if(repeatCounter > 0) - { - Console.WriteLine("wiederhole telegrmm " + message.SequenceCounter.ToString()); - } - if(repeatCounter > 3) - throw new Exception("Zu viele wiederholungen eines Telegramms auf kein OK"); - - foreach (UdpClient client in _udpList) - await client.SendAsync(xdata.ToArray(), xdata.Count, _sendEndPoint); - - _ackToken = new CancellationTokenSource(); - - try{ + int repeatCounter = 0; + do + { + // if(repeatCounter > 0) + // { + // Console.WriteLine("wiederhole telegrmm " + message.SequenceCounter.ToString()); + // } + if(repeatCounter > 3) + throw new Exception("Zu viele wiederholungen eines Telegramms auf kein OK"); + + await _client.SendAsync(xdata.ToArray()); + + _ackToken = new CancellationTokenSource(); + + try{ await Task.Delay(1000, _ackToken.Token); - }catch{} - _ackToken = null; + }catch{} + _ackToken = null; - repeatCounter++; - } while(!_receivedAcks.Contains(message.SequenceCounter)); - } - else - { - throw new Exception("Unbekanntes Element in SendQueue! " + sendMessage.GetType().FullName); - } + repeatCounter++; + } while(!_receivedAcks.Contains(message.SequenceCounter)); + } + else + { + throw new Exception("Unbekanntes Element in SendQueue! " + sendMessage.GetType().FullName); } - }); + } + } + + public void Dispose() + { + if(_client != null) + _client.Dispose(); + tokenSource.Cancel(); } } -} +} \ No newline at end of file diff --git a/Kaenx.Konnect/Connections/UdpConnection.cs b/Kaenx.Konnect/Connections/UdpConnection.cs new file mode 100644 index 0000000..de4df10 --- /dev/null +++ b/Kaenx.Konnect/Connections/UdpConnection.cs @@ -0,0 +1,80 @@ +using System; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Kaenx.Konnect.Classes; +using Kaenx.Konnect.Messages; + +namespace Kaenx.Konnect.Connections +{ + internal class UdpConnection : IDisposable + { + private CancellationTokenSource tokenSource = new CancellationTokenSource(); + private UdpClient client; + private IPEndPoint Source { get; set; } + private IPEndPoint Target { get; set; } + public delegate void ReceivedKnxMessage(UdpConnection sender, IParserMessage message); + public event ReceivedKnxMessage OnReceived; + public NetworkInterface Interface { get; set; } + public int InterfaceIndex { get; set; } = 0; + + public UdpConnection(IPAddress ip, IPEndPoint _target, bool isMulticast = false, IPEndPoint _source = null) + { + client = new UdpClient(new IPEndPoint(ip, 0)); + client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false); + if(isMulticast) { + client.Client.MulticastLoopback = false; + client.MulticastLoopback = false; + client.JoinMulticastGroup(_target.Address, ip); + } + Task.Run(ProcessReceive, tokenSource.Token); + Target = _target; + Source = _source; + } + + public UdpConnection(IPAddress ip, int port, IPEndPoint _target, bool isMulticast = false, IPEndPoint _source = null) + { + client = new UdpClient(new IPEndPoint(ip, port)); + client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false); + if(isMulticast) { + client.Client.MulticastLoopback = false; + client.MulticastLoopback = false; + client.JoinMulticastGroup(_target.Address, ip); + } + Task.Run(ProcessReceive, tokenSource.Token); + Target = _target; + Source = _source; + } + + public async Task SendAsync(byte[] data) + { + await client.SendAsync(data, data.Length, Target); + } + + public IPEndPoint GetLocalEndpoint() + { + return client.Client.LocalEndPoint as IPEndPoint; + } + + private async void ProcessReceive() + { + while(true) + { + var result = await client.ReceiveAsync(); + var knxResponse = ReceiverParserDispatcher.Instance.Build(result.Buffer); + OnReceived?.Invoke(this, knxResponse); + } + } + + public void Dispose() + { + tokenSource.Cancel(); + client.Close(); + client.Dispose(); + } + } +} \ No newline at end of file diff --git a/Kaenx.Konnect/Exceptions/DeviceNotConnectedException.cs b/Kaenx.Konnect/Exceptions/DeviceNotConnectedException.cs new file mode 100644 index 0000000..68f0fb7 --- /dev/null +++ b/Kaenx.Konnect/Exceptions/DeviceNotConnectedException.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kaenx.Konnect.Exceptions +{ + public class DeviceNotConnectedException : Exception + { + } +} diff --git a/Kaenx.Konnect/Exceptions/NotSupportedException.cs b/Kaenx.Konnect/Exceptions/NotSupportedException.cs index 4298620..7db6f32 100644 --- a/Kaenx.Konnect/Exceptions/NotSupportedException.cs +++ b/Kaenx.Konnect/Exceptions/NotSupportedException.cs @@ -6,5 +6,8 @@ namespace Kaenx.Konnect.Exceptions { public class NotSupportedException : Exception { + public NotSupportedException() : base() { } + public NotSupportedException(string message) : base(message) { } + public NotSupportedException(string message, Exception innerException) : base(message, innerException) { } } } diff --git a/Kaenx.Konnect/Kaenx.Konnect.csproj b/Kaenx.Konnect/Kaenx.Konnect.csproj index 03a036f..c3fe6f6 100644 --- a/Kaenx.Konnect/Kaenx.Konnect.csproj +++ b/Kaenx.Konnect/Kaenx.Konnect.csproj @@ -7,10 +7,10 @@ net7.0 true de - 1.0.0.0 - 1.0.0.0 - Bibliothek für den Zugriff auf den KNX Bus mittels IP oder USB. - 1.0.0 + 1.0.1.0 + 1.0.1.0 + Connect to a KNX IP (tunneling/routing) or USB Interface + 1.0.1 Logo.png knx, bus, communication @@ -20,7 +20,7 @@ https://github.com/OpenKnx/Kaenx-Konnect Github true - 2023 - Mike Gerst + 2024 - Mike Gerst 8.0 Logo.ico diff --git a/Kaenx.Konnect/Parser/ConnectResponseParser.cs b/Kaenx.Konnect/Parser/ConnectResponseParser.cs index 8750bdd..8cdfad2 100644 --- a/Kaenx.Konnect/Parser/ConnectResponseParser.cs +++ b/Kaenx.Konnect/Parser/ConnectResponseParser.cs @@ -35,8 +35,17 @@ private static ConnectionResponseDataBlock ParseConnectionResponseDataBlock(IEnu if (bytes.Count() == 0) return new ConnectionResponseDataBlock(0x00, 0x01, UnicastAddress.FromString("0.0.0")); var enumerable = bytes as byte[] ?? bytes.ToArray(); - return new ConnectionResponseDataBlock(enumerable.ElementAt(0), enumerable.ElementAt(1), - UnicastAddress.FromByteArray(enumerable.Skip(2).ToArray())); + + if(enumerable.ElementAt(1) == 0x04) + { + //Wenn Tunneling + return new ConnectionResponseDataBlock(enumerable.ElementAt(0), enumerable.ElementAt(1), + UnicastAddress.FromByteArray(enumerable.Skip(2).ToArray())); + } else { + //Wenn Config + return new ConnectionResponseDataBlock(enumerable.ElementAt(0), enumerable.ElementAt(1), + UnicastAddress.FromString("0.0.0")); + } } private static HostProtocolAddressInformation ParseEndpoint(IEnumerable bytes)