| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package knxnetip |
| |
| import ( |
| "context" |
| "reflect" |
| "time" |
| |
| "github.com/pkg/errors" |
| |
| driverModel "github.com/apache/plc4x/plc4go/protocols/knxnetip/readwrite/model" |
| "github.com/apache/plc4x/plc4go/spi" |
| "github.com/apache/plc4x/plc4go/spi/options" |
| "github.com/apache/plc4x/plc4go/spi/utils" |
| ) |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // KnxNetIpConnection internal operations |
| // |
| // These are used internally by functions of the KnxNetIpConnection. |
| // |
| // All of the sendXYZ functions take care of sending a request and waiting for the matching |
| // response. They don't actually process the data in the response, they just handle receiving |
| // it and returning it to the calling function. |
| // |
| // They all assume the connection is checked and is available. |
| /////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| func (m *Connection) sendGatewaySearchRequest(ctx context.Context) (driverModel.SearchResponse, error) { |
| localAddress, err := m.getLocalAddress() |
| if err != nil { |
| return nil, errors.Wrap(err, "error getting local address") |
| } |
| |
| localAddr := driverModel.NewIPAddress(localAddress.IP) |
| discoveryEndpoint := driverModel.NewHPAIDiscoveryEndpoint( |
| driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port), |
| ) |
| searchRequest := driverModel.NewSearchRequest(discoveryEndpoint) |
| |
| result := make(chan driverModel.SearchResponse, 1) |
| errorResult := make(chan error, 1) |
| err = m.messageCodec.SendRequest(ctx, searchRequest, |
| func(message spi.Message) bool { |
| _, ok := message.(driverModel.SearchResponse) |
| return ok |
| }, |
| func(message spi.Message) error { |
| searchResponse := message.(driverModel.SearchResponse) |
| result <- searchResponse |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending search request") |
| } |
| |
| ttlTimer := time.NewTimer(m.defaultTtl) |
| select { |
| case response := <-result: |
| if !ttlTimer.Stop() { |
| <-ttlTimer.C |
| } |
| return response, nil |
| case errorResponse := <-errorResult: |
| if !ttlTimer.Stop() { |
| <-ttlTimer.C |
| } |
| return nil, errorResponse |
| // For search requests there is no timeout handler running, so we have to do it manually. |
| case <-ttlTimer.C: |
| ttlTimer.Stop() |
| return nil, errors.New("timeout") |
| } |
| } |
| |
| func (m *Connection) sendGatewayConnectionRequest(ctx context.Context) (driverModel.ConnectionResponse, error) { |
| localAddress, err := m.getLocalAddress() |
| if err != nil { |
| return nil, errors.Wrap(err, "error getting local address") |
| } |
| |
| localAddr := driverModel.NewIPAddress(localAddress.IP[len(localAddress.IP)-4:]) |
| connectionRequest := driverModel.NewConnectionRequest( |
| driverModel.NewHPAIDiscoveryEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)), |
| driverModel.NewHPAIDataEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)), |
| driverModel.NewConnectionRequestInformationTunnelConnection(driverModel.KnxLayer_TUNNEL_LINK_LAYER), |
| ) |
| |
| result := make(chan driverModel.ConnectionResponse, 1) |
| errorResult := make(chan error, 1) |
| err = m.messageCodec.SendRequest(ctx, connectionRequest, |
| func(message spi.Message) bool { |
| _, ok := message.(driverModel.ConnectionResponse) |
| return ok |
| }, |
| func(message spi.Message) error { |
| connectionResponse := message.(driverModel.ConnectionResponse) |
| result <- connectionResponse |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendGatewayDisconnectionRequest(ctx context.Context) (driverModel.DisconnectResponse, error) { |
| localAddress, err := m.getLocalAddress() |
| if err != nil { |
| return nil, errors.Wrap(err, "error getting local address") |
| } |
| |
| localAddr := driverModel.NewIPAddress(localAddress.IP[len(localAddress.IP)-4:]) |
| disconnectRequest := driverModel.NewDisconnectRequest( |
| m.CommunicationChannelId, |
| driverModel.NewHPAIControlEndpoint( |
| driverModel.HostProtocolCode_IPV4_UDP, |
| localAddr, |
| uint16(localAddress.Port), |
| ), |
| ) |
| |
| result := make(chan driverModel.DisconnectResponse, 1) |
| errorResult := make(chan error, 1) |
| err = m.messageCodec.SendRequest(ctx, disconnectRequest, |
| func(message spi.Message) bool { |
| _, ok := message.(driverModel.DisconnectResponse) |
| return ok |
| }, |
| func(message spi.Message) error { |
| disconnectResponse := message.(driverModel.DisconnectResponse) |
| result <- disconnectResponse |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendConnectionStateRequest(ctx context.Context) (driverModel.ConnectionStateResponse, error) { |
| localAddress, err := m.getLocalAddress() |
| if err != nil { |
| return nil, errors.Wrap(err, "error getting local address") |
| } |
| |
| localAddr := driverModel.NewIPAddress(localAddress.IP[len(localAddress.IP)-4:]) |
| connectionStateRequest := driverModel.NewConnectionStateRequest( |
| m.CommunicationChannelId, |
| driverModel.NewHPAIControlEndpoint( |
| driverModel.HostProtocolCode_IPV4_UDP, |
| localAddr, uint16(localAddress.Port))) |
| |
| result := make(chan driverModel.ConnectionStateResponse, 1) |
| errorResult := make(chan error, 1) |
| err = m.messageCodec.SendRequest(ctx, connectionStateRequest, |
| func(message spi.Message) bool { |
| _, ok := message.(driverModel.ConnectionStateResponse) |
| return ok |
| }, |
| func(message spi.Message) error { |
| connectionStateResponse := message.(driverModel.ConnectionStateResponse) |
| result <- connectionStateResponse |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendGroupAddressReadRequest(ctx context.Context, groupAddress []byte) (driverModel.ApduDataGroupValueResponse, error) { |
| // Send the property read request and wait for a confirmation that this property is readable. |
| groupAddressReadRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_LOW, |
| false, |
| false, |
| true, |
| 6, |
| 0, |
| m.ClientKnxAddress, groupAddress, |
| driverModel.NewApduDataContainer( |
| false, |
| 0, |
| driverModel.NewApduDataGroupValueRead(), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduDataGroupValueResponse, 1) |
| errorResult := make(chan error, 1) |
| err := m.messageCodec.SendRequest(ctx, groupAddressReadRequest, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataInd, ok := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| dataContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| if !ok { |
| return false |
| } |
| _, ok = dataContainer.GetDataApdu().(driverModel.ApduDataGroupValueResponse) |
| if !ok { |
| return false |
| } |
| // Check if it's a value response for the given group address |
| return dataFrameExt.GetGroupAddress() && reflect.DeepEqual(dataFrameExt.GetSourceAddress(), groupAddress) |
| }, |
| func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataInd := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| dataFrameExt := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| dataContainer := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| groupReadResponse := dataContainer.GetDataApdu().(driverModel.ApduDataGroupValueResponse) |
| |
| result <- groupReadResponse |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDeviceConnectionRequest(ctx context.Context, targetAddress driverModel.KnxAddress) (driverModel.ApduControlConnect, error) { |
| // Send a connection request to the individual KNX device |
| deviceConnectionRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_SYSTEM, |
| false, |
| false, |
| false, |
| 6, |
| uint8(0), |
| driverModel.NewKnxAddress(0, 0, 0), KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduControlContainer( |
| false, |
| 0, |
| driverModel.NewApduControlConnect(), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduControlConnect, 1) |
| errorResult := make(chan error, 1) |
| err := m.messageCodec.SendRequest(ctx, deviceConnectionRequest, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataCon, ok := tunnelingRequest.GetCemi().(driverModel.LDataCon) |
| if !ok { |
| return false |
| } |
| lDataFrameExt, ok := lDataCon.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| // Check if the address matches |
| ctxForModel := options.GetLoggerContextForModel(ctx, m.log, options.WithPassLoggerToModel(m.passLogToModel)) |
| if ByteArrayToKnxAddress(ctxForModel, lDataFrameExt.GetDestinationAddress()) != targetAddress { |
| return false |
| } |
| apduControlContainer, ok := lDataFrameExt.GetApdu().(driverModel.ApduControlContainer) |
| if !ok { |
| return false |
| } |
| _, ok = apduControlContainer.GetControlApdu().(driverModel.ApduControlConnect) |
| return ok |
| }, |
| func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataCon := tunnelingRequest.GetCemi().(driverModel.LDataCon) |
| lDataFrameExt := lDataCon.GetDataFrame().(driverModel.LDataExtended) |
| apduControlContainer := lDataFrameExt.GetApdu().(driverModel.ApduControlContainer) |
| apduControlConnect := apduControlContainer.GetControlApdu().(driverModel.ApduControlConnect) |
| |
| // If the error flag is set, there was an error connecting |
| if lDataCon.GetDataFrame().GetErrorFlag() { |
| errorResult <- errors.Errorf("error connecting to device at: %s", KnxAddressToString(targetAddress)) |
| } else { |
| result <- apduControlConnect |
| } |
| |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDeviceDisconnectionRequest(ctx context.Context, targetAddress driverModel.KnxAddress) (driverModel.ApduControlDisconnect, error) { |
| // Send a connection request to the individual KNX device |
| deviceDisconnectionRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_SYSTEM, |
| false, |
| false, |
| false, |
| 6, |
| uint8(0), |
| driverModel.NewKnxAddress(0, 0, 0), KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduControlContainer( |
| false, |
| 0, |
| driverModel.NewApduControlDisconnect(), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduControlDisconnect, 1) |
| errorResult := make(chan error, 1) |
| if err := m.messageCodec.SendRequest(ctx, deviceDisconnectionRequest, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataCon, ok := tunnelingRequest.GetCemi().(driverModel.LDataCon) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataCon.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| ctxForModel := options.GetLoggerContextForModel(ctx, m.log, options.WithPassLoggerToModel(m.passLogToModel)) |
| curTargetAddress := ByteArrayToKnxAddress(ctxForModel, dataFrameExt.GetDestinationAddress()) |
| // Check if the address matches |
| if curTargetAddress != targetAddress { |
| return false |
| } |
| apduControlContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduControlContainer) |
| if !ok { |
| return false |
| } |
| apduControlDisconnect := apduControlContainer.GetControlApdu().(driverModel.ApduControlDisconnect) |
| return apduControlDisconnect != nil |
| }, |
| func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataCon := tunnelingRequest.GetCemi().(driverModel.LDataCon) |
| dataFrameExt := lDataCon.GetDataFrame().(driverModel.LDataExtended) |
| apduControlContainer := dataFrameExt.GetApdu().(driverModel.ApduControlContainer) |
| apduControlDisconnect := apduControlContainer.GetControlApdu().(driverModel.ApduControlDisconnect) |
| |
| // If the error flag is set, there was an error disconnecting |
| if lDataCon.GetDataFrame().GetErrorFlag() { |
| errorResult <- errors.Errorf("error disconnecting from device at: %s", KnxAddressToString(targetAddress)) |
| } else { |
| result <- apduControlDisconnect |
| } |
| |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ); err != nil { |
| return nil, errors.Wrap(err, "got error sending request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDeviceAuthentication(ctx context.Context, targetAddress driverModel.KnxAddress, authenticationLevel uint8, buildingKey []byte) (driverModel.ApduDataExtAuthorizeResponse, error) { |
| // Check if there is already a connection available, |
| // if not, create a new one. |
| connection, ok := m.DeviceConnections[targetAddress] |
| if !ok { |
| return nil, errors.New("not connected") |
| } |
| |
| // Send a connection request to the individual KNX device |
| counter := connection.counter |
| connection.counter++ |
| deviceAuthenticationRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_SYSTEM, |
| false, |
| false, |
| false, |
| 6, |
| uint8(0), |
| driverModel.NewKnxAddress(0, 0, 0), KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduDataContainer( |
| true, |
| counter, |
| driverModel.NewApduDataOther( |
| driverModel.NewApduDataExtAuthorizeRequest(authenticationLevel, buildingKey), |
| ), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduDataExtAuthorizeResponse, 1) |
| errorResult := make(chan error, 1) |
| if err := m.messageCodec.SendRequest(ctx, deviceAuthenticationRequest, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataInd := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| apduDataContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| if !ok { |
| return false |
| } |
| apduDataOther, ok := apduDataContainer.GetDataApdu().(driverModel.ApduDataOther) |
| if !ok { |
| return false |
| } |
| _, ok = apduDataOther.GetExtendedApdu().(driverModel.ApduDataExtAuthorizeResponse) |
| if !ok { |
| return false |
| } |
| ctxForModel := options.GetLoggerContextForModel(ctx, m.log, options.WithPassLoggerToModel(m.passLogToModel)) |
| curTargetAddress := ByteArrayToKnxAddress(ctxForModel, dataFrameExt.GetDestinationAddress()) |
| // Check if the addresses match |
| if curTargetAddress != m.ClientKnxAddress { |
| return false |
| } |
| if dataFrameExt.GetSourceAddress() != targetAddress { |
| return false |
| } |
| // Check if the counter matches |
| if dataFrameExt.GetApdu().GetCounter() != counter { |
| return false |
| } |
| return true |
| }, func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataInd := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| dataFrameExt := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| apduDataContainer := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| apduDataOther := apduDataContainer.GetDataApdu().(driverModel.ApduDataOther) |
| apduAuthorizeResponse := apduDataOther.GetExtendedApdu().(driverModel.ApduDataExtAuthorizeResponse) |
| |
| // Acknowledge the receipt |
| _ = m.sendDeviceAck(ctx, targetAddress, dataFrameExt.GetApdu().GetCounter(), func(err error) { |
| // If the error flag is set, there was an error authenticating |
| if lDataInd.GetDataFrame().GetErrorFlag() { |
| errorResult <- errors.New("error authenticating at device: " + KnxAddressToString(targetAddress)) |
| } else if err != nil { |
| errorResult <- errors.Wrapf(err, "error sending ack to device: %s", KnxAddressToString(targetAddress)) |
| } else { |
| result <- apduAuthorizeResponse |
| } |
| }) |
| |
| return nil |
| }, func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ); err != nil { |
| return nil, errors.Wrap(err, "got error sending request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDeviceDeviceDescriptorReadRequest(ctx context.Context, targetAddress driverModel.KnxAddress) (driverModel.ApduDataDeviceDescriptorResponse, error) { |
| // Next, read the device descriptor so we know how we have to communicate with the device. |
| counter := m.getNextCounter(targetAddress) |
| deviceDescriptorReadRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_LOW, |
| false, |
| false, |
| false, |
| 6, |
| uint8(0), |
| driverModel.NewKnxAddress(0, 0, 0), |
| KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduDataContainer( |
| true, |
| counter, |
| driverModel.NewApduDataDeviceDescriptorRead(0), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduDataDeviceDescriptorResponse, 1) |
| errorResult := make(chan error, 1) |
| err := m.messageCodec.SendRequest(ctx, deviceDescriptorReadRequest, func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataInd, ok := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| // Check if the address matches |
| if dataFrameExt.GetSourceAddress() != targetAddress { |
| return false |
| } |
| // Check if the counter matches |
| if dataFrameExt.GetApdu().GetCounter() != counter { |
| return false |
| } |
| dataContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| if !ok { |
| return false |
| } |
| _, ok = dataContainer.GetDataApdu().(driverModel.ApduDataDeviceDescriptorResponse) |
| if !ok { |
| return false |
| } |
| return true |
| }, func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataInd := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| dataFrame := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| dataContainer := dataFrame.GetApdu().(driverModel.ApduDataContainer) |
| deviceDescriptorResponse := dataContainer.GetDataApdu().(driverModel.ApduDataDeviceDescriptorResponse) |
| |
| // Acknowledge the receipt |
| _ = m.sendDeviceAck(ctx, targetAddress, dataFrame.GetApdu().GetCounter(), func(err error) { |
| // If the error flag is set, there was an error authenticating |
| if lDataInd.GetDataFrame().GetErrorFlag() { |
| errorResult <- errors.New("error reading device descriptor from device: " + KnxAddressToString(targetAddress)) |
| } else if err != nil { |
| errorResult <- errors.Wrapf(err, "error sending ack to device: %s", KnxAddressToString(targetAddress)) |
| } else { |
| result <- deviceDescriptorResponse |
| } |
| }) |
| |
| return nil |
| }, func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, m.defaultTtl) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending device descriptor read request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDevicePropertyReadRequest(ctx context.Context, targetAddress driverModel.KnxAddress, objectId uint8, propertyId uint8, propertyIndex uint16, numElements uint8) (driverModel.ApduDataExtPropertyValueResponse, error) { |
| // Next, read the device descriptor so we know how we have to communicate with the device. |
| // Send the property read request and wait for a confirmation that this property is readable. |
| counter := m.getNextCounter(targetAddress) |
| propertyReadRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_LOW, |
| false, |
| false, |
| false, |
| 6, |
| 0, |
| driverModel.NewKnxAddress(0, 0, 0), |
| KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduDataContainer( |
| true, |
| counter, |
| driverModel.NewApduDataOther( |
| driverModel.NewApduDataExtPropertyValueRead(objectId, propertyId, numElements, propertyIndex), |
| ), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduDataExtPropertyValueResponse, 1) |
| errorResult := make(chan error, 1) |
| if err := m.messageCodec.SendRequest(ctx, propertyReadRequest, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataInd, ok := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| // Check if the address matches |
| if dataFrameExt.GetSourceAddress() != targetAddress { |
| return false |
| } |
| // Check if the counter matches |
| if dataFrameExt.GetApdu().GetCounter() != counter { |
| return false |
| } |
| dataContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| if !ok { |
| return false |
| } |
| dataApduOther, ok := dataContainer.GetDataApdu().(driverModel.ApduDataOther) |
| if !ok { |
| return false |
| } |
| propertyValueResponse, ok := dataApduOther.GetExtendedApdu().(driverModel.ApduDataExtPropertyValueResponse) |
| if !ok { |
| return false |
| } |
| return propertyValueResponse.GetObjectIndex() == objectId && propertyValueResponse.GetPropertyId() == propertyId |
| }, |
| func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataInd := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| dataFrameExt := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| dataContainer := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| dataApduOther := dataContainer.GetDataApdu().(driverModel.ApduDataOther) |
| propertyValueResponse := dataApduOther.GetExtendedApdu().(driverModel.ApduDataExtPropertyValueResponse) |
| |
| // Acknowledge the receipt |
| _ = m.sendDeviceAck(ctx, targetAddress, dataFrameExt.GetApdu().GetCounter(), func(err error) { |
| // If the error flag is set, there was an error authenticating |
| if lDataInd.GetDataFrame().GetErrorFlag() { |
| errorResult <- errors.New("error reading property value from device: " + KnxAddressToString(targetAddress)) |
| } else if err != nil { |
| errorResult <- errors.Wrapf(err, "error sending ack to device: %s", KnxAddressToString(targetAddress)) |
| } else { |
| result <- propertyValueResponse |
| } |
| }) |
| |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ); err != nil { |
| return nil, errors.Wrap(err, "got error sending device property read request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDevicePropertyDescriptionReadRequest(ctx context.Context, targetAddress driverModel.KnxAddress, objectId uint8, propertyId uint8) (driverModel.ApduDataExtPropertyDescriptionResponse, error) { |
| // Next, read the device descriptor so we know how we have to communicate with the device. |
| // Send the property read request and wait for a confirmation that this property is readable. |
| counter := m.getNextCounter(targetAddress) |
| propertyReadRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_LOW, |
| false, |
| false, |
| false, |
| 6, |
| 0, |
| driverModel.NewKnxAddress(0, 0, 0), |
| KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduDataContainer( |
| true, |
| counter, |
| driverModel.NewApduDataOther( |
| driverModel.NewApduDataExtPropertyDescriptionRead(objectId, propertyId, 1), |
| ), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduDataExtPropertyDescriptionResponse, 1) |
| errorResult := make(chan error, 1) |
| err := m.messageCodec.SendRequest(ctx, propertyReadRequest, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataInd, ok := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| // Check if the address matches |
| if dataFrameExt.GetSourceAddress() != targetAddress { |
| return false |
| } |
| // Check if the counter matches |
| if dataFrameExt.GetApdu().GetCounter() != counter { |
| return false |
| } |
| dataContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| if !ok { |
| return false |
| } |
| dataApduOther, ok := dataContainer.GetDataApdu().(driverModel.ApduDataOther) |
| if !ok { |
| return false |
| } |
| propertyDescriptionResponse, ok := dataApduOther.GetExtendedApdu().(driverModel.ApduDataExtPropertyDescriptionResponse) |
| if !ok { |
| return false |
| } |
| return propertyDescriptionResponse.GetObjectIndex() == objectId && propertyDescriptionResponse.GetPropertyId() == propertyId |
| }, |
| func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataInd := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| dataFrameExt := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| dataContainer := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| dataApduOther := dataContainer.GetDataApdu().(driverModel.ApduDataOther) |
| propertyDescriptionResponse := dataApduOther.GetExtendedApdu().(driverModel.ApduDataExtPropertyDescriptionResponse) |
| |
| // Acknowledge the receipt |
| _ = m.sendDeviceAck(ctx, targetAddress, dataFrameExt.GetApdu().GetCounter(), func(err error) { |
| // If the error flag is set, there was an error authenticating |
| if lDataInd.GetDataFrame().GetErrorFlag() { |
| errorResult <- errors.Errorf("error reading property description from device: %s", KnxAddressToString(targetAddress)) |
| } else if err != nil { |
| errorResult <- errors.Wrapf(err, "error sending ack to device: %s", KnxAddressToString(targetAddress)) |
| } else { |
| result <- propertyDescriptionResponse |
| } |
| }) |
| |
| return nil |
| }, func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrapf(err, "got error processing request") |
| return nil |
| }, m.defaultTtl) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "got error sending property description read request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDeviceMemoryReadRequest(ctx context.Context, targetAddress driverModel.KnxAddress, address uint16, numBytes uint8) (driverModel.ApduDataMemoryResponse, error) { |
| // Next, read the device descriptor so we know how we have to communicate with the device. |
| counter := m.getNextCounter(targetAddress) |
| |
| // Send the property read request and wait for a confirmation that this property is readable. |
| propertyReadRequest := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_LOW, |
| false, |
| false, |
| false, |
| 6, |
| 0, |
| driverModel.NewKnxAddress(0, 0, 0), |
| KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduDataContainer( |
| true, |
| counter, |
| driverModel.NewApduDataMemoryRead(numBytes, address), |
| ), |
| ), |
| ), |
| ) |
| |
| result := make(chan driverModel.ApduDataMemoryResponse, 1) |
| errorResult := make(chan error, 1) |
| if err := m.messageCodec.SendRequest(ctx, propertyReadRequest, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || |
| tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataInd, ok := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| dataContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| if !ok { |
| return false |
| } |
| dataApduMemoryResponse, ok := dataContainer.GetDataApdu().(driverModel.ApduDataMemoryResponse) |
| if !ok { |
| return false |
| } |
| |
| // Check if the address matches |
| if dataFrameExt.GetSourceAddress() != targetAddress { |
| return false |
| } |
| // Check if the counter matches |
| if dataFrameExt.GetApdu().GetCounter() != counter { |
| return false |
| } |
| return dataApduMemoryResponse.GetAddress() == address |
| }, |
| func(message spi.Message) error { |
| tunnelingRequest := message.(driverModel.TunnelingRequest) |
| lDataInd := tunnelingRequest.GetCemi().(driverModel.LDataInd) |
| dataFrameExt := lDataInd.GetDataFrame().(driverModel.LDataExtended) |
| dataContainer := dataFrameExt.GetApdu().(driverModel.ApduDataContainer) |
| dataApduMemoryResponse := dataContainer.GetDataApdu().(driverModel.ApduDataMemoryResponse) |
| |
| // Acknowledge the receipt |
| _ = m.sendDeviceAck(ctx, targetAddress, dataFrameExt.GetApdu().GetCounter(), func(err error) { |
| // If the error flag is set, there was an error authenticating |
| if lDataInd.GetDataFrame().GetErrorFlag() { |
| errorResult <- errors.Errorf("error reading memory from device: %s", KnxAddressToString(targetAddress)) |
| } else if err != nil { |
| errorResult <- errors.Errorf("error sending ack to device: %s", KnxAddressToString(targetAddress)) |
| } else { |
| result <- dataApduMemoryResponse |
| } |
| }) |
| |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| errorResult <- errors.Wrap(err, "got error processing request") |
| return nil |
| }, |
| m.defaultTtl, |
| ); err != nil { |
| return nil, errors.Wrap(err, "got error sending memory read request") |
| } |
| |
| select { |
| case response := <-result: |
| return response, nil |
| case errorResponse := <-errorResult: |
| return nil, errorResponse |
| } |
| } |
| |
| func (m *Connection) sendDeviceAck(ctx context.Context, targetAddress driverModel.KnxAddress, counter uint8, callback func(err error)) error { |
| ack := driverModel.NewTunnelingRequest( |
| driverModel.NewTunnelingRequestDataBlock(m.CommunicationChannelId, m.getNewSequenceCounter()), |
| driverModel.NewLDataReq( |
| 0, |
| nil, |
| driverModel.NewLDataExtended( |
| true, |
| true, |
| driverModel.CEMIPriority_SYSTEM, |
| false, |
| false, |
| false, |
| 6, |
| uint8(0), |
| driverModel.NewKnxAddress(0, 0, 0), KnxAddressToByteArray(targetAddress), |
| driverModel.NewApduControlContainer(true, counter, driverModel.NewApduControlAck()), |
| ), |
| ), |
| ) |
| |
| if err := m.messageCodec.SendRequest(ctx, ack, |
| func(message spi.Message) bool { |
| tunnelingRequest, ok := message.(driverModel.TunnelingRequest) |
| if !ok || |
| tunnelingRequest.GetTunnelingRequestDataBlock().GetCommunicationChannelId() != m.CommunicationChannelId { |
| return false |
| } |
| lDataCon, ok := tunnelingRequest.GetCemi().(driverModel.LDataCon) |
| if !ok { |
| return false |
| } |
| dataFrameExt, ok := lDataCon.GetDataFrame().(driverModel.LDataExtended) |
| if !ok { |
| return false |
| } |
| // Check if the addresses match |
| if dataFrameExt.GetSourceAddress() != m.ClientKnxAddress { |
| return false |
| } |
| ctxForModel := options.GetLoggerContextForModel(ctx, m.log, options.WithPassLoggerToModel(m.passLogToModel)) |
| curTargetAddress := ByteArrayToKnxAddress(ctxForModel, dataFrameExt.GetDestinationAddress()) |
| if curTargetAddress != targetAddress { |
| return false |
| } |
| // Check if the counter matches |
| if dataFrameExt.GetApdu().GetCounter() != counter { |
| return false |
| } |
| controlContainer, ok := dataFrameExt.GetApdu().(driverModel.ApduControlContainer) |
| if !ok { |
| return false |
| } |
| _, ok = controlContainer.GetControlApdu().(driverModel.ApduControlAck) |
| return ok |
| }, |
| func(message spi.Message) error { |
| callback(nil) |
| return nil |
| }, |
| func(err error) error { |
| // If this is a timeout, do a check if the connection requires a reconnection |
| var timeoutError utils.TimeoutError |
| if errors.As(err, &timeoutError) { |
| m.handleTimeout() |
| } |
| callback(errors.Wrap(err, "got error processing request")) |
| return nil |
| }, |
| m.defaultTtl, |
| ); err != nil { |
| return errors.Wrap(err, "got error sending ack request") |
| } |
| |
| return nil |
| } |