| /* |
| * 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 ads |
| |
| import ( |
| "context" |
| "encoding/binary" |
| "fmt" |
| "strings" |
| |
| "github.com/apache/plc4x/plc4go/internal/ads/model" |
| apiModel "github.com/apache/plc4x/plc4go/pkg/api/model" |
| "github.com/apache/plc4x/plc4go/pkg/api/values" |
| driverModel "github.com/apache/plc4x/plc4go/protocols/ads/readwrite/model" |
| internalModel "github.com/apache/plc4x/plc4go/spi/model" |
| "github.com/apache/plc4x/plc4go/spi/utils" |
| internalValues "github.com/apache/plc4x/plc4go/spi/values" |
| "github.com/pkg/errors" |
| "github.com/rs/zerolog/log" |
| ) |
| |
| func (m *Connection) ReadRequestBuilder() apiModel.PlcReadRequestBuilder { |
| return internalModel.NewDefaultPlcReadRequestBuilder(m.GetPlcTagHandler(), m) |
| } |
| |
| func (m *Connection) Read(ctx context.Context, readRequest apiModel.PlcReadRequest) <-chan apiModel.PlcReadRequestResult { |
| log.Trace().Msg("Reading") |
| result := make(chan apiModel.PlcReadRequestResult) |
| go func() { |
| if len(readRequest.GetTagNames()) <= 1 { |
| m.singleRead(ctx, readRequest, result) |
| } else { |
| m.multiRead(ctx, readRequest, result) |
| } |
| }() |
| return result |
| } |
| |
| func (m *Connection) singleRead(ctx context.Context, readRequest apiModel.PlcReadRequest, result chan apiModel.PlcReadRequestResult) { |
| if len(readRequest.GetTagNames()) != 1 { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.New("this part of the ads driver only supports single-item requests"), |
| } |
| log.Debug().Msgf("this part of the ads driver only supports single-item requests. Got %d tags", len(readRequest.GetTagNames())) |
| return |
| } |
| |
| // Here we can be sure that we're only handling a single request. |
| tagName := readRequest.GetTagNames()[0] |
| tag := readRequest.GetTag(tagName) |
| if model.NeedsResolving(tag) { |
| adsField, err := model.CastToSymbolicPlcTagFromPlcTag(tag) |
| if err != nil { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrap(err, "invalid tag item type"), |
| } |
| log.Debug().Msgf("Invalid tag item type %T", tag) |
| return |
| } |
| // Replace the symbolic tag with a direct one |
| tag, err = m.resolveSymbolicTag(ctx, adsField) |
| if err != nil { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrap(err, "invalid tag item type"), |
| } |
| log.Debug().Msgf("Invalid tag item type %T", tag) |
| return |
| } |
| } |
| directAdsTag, ok := tag.(*model.DirectPlcTag) |
| if !ok { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.New("invalid tag item type"), |
| } |
| log.Debug().Msgf("Invalid tag item type %T", tag) |
| return |
| } |
| |
| go func() { |
| response, err := m.ExecuteAdsReadRequest(ctx, directAdsTag.IndexGroup, directAdsTag.IndexOffset, directAdsTag.DataType.GetSize()) |
| if err != nil { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Err: errors.Wrap(err, "got error executing the read request"), |
| } |
| return |
| } |
| |
| if response.GetErrorCode() != 0x00000000 { |
| // TODO: Handle this ... |
| } |
| |
| rb := utils.NewReadBufferByteBased(response.GetData(), utils.WithByteOrderForReadBufferByteBased(binary.LittleEndian)) |
| responseCodes := map[string]apiModel.PlcResponseCode{} |
| plcValues := map[string]values.PlcValue{} |
| for _, tagName := range readRequest.GetTagNames() { |
| log.Debug().Msgf("get a tag from request with name %s", tagName) |
| // Try to parse the value |
| plcValue, err := m.parsePlcValue(directAdsTag.DataType, directAdsTag.DataType.GetArrayInfo(), rb) |
| if err != nil { |
| log.Error().Err(err).Msg("Error parsing plc value") |
| responseCodes[tagName] = apiModel.PlcResponseCode_INTERNAL_ERROR |
| } else { |
| plcValues[tagName] = plcValue |
| responseCodes[tagName] = apiModel.PlcResponseCode_OK |
| } |
| } |
| // Return the response to the caller. |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: internalModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues), |
| Err: nil, |
| } |
| }() |
| } |
| |
| func (m *Connection) multiRead(ctx context.Context, readRequest apiModel.PlcReadRequest, result chan apiModel.PlcReadRequestResult) { |
| // Calculate the size of all tags together. |
| // Calculate the expected size of the response data. |
| expectedResponseDataSize := uint32(0) |
| directAdsTags := map[string]*model.DirectPlcTag{} |
| requestItems := make([]driverModel.AdsMultiRequestItem, 0) |
| for _, tagName := range readRequest.GetTagNames() { |
| tag := readRequest.GetTag(tagName) |
| if model.NeedsResolving(tag) { |
| adsField, err := model.CastToSymbolicPlcTagFromPlcTag(tag) |
| if err != nil { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrap(err, "invalid tag item type"), |
| } |
| log.Debug().Msgf("Invalid tag item type %T", tag) |
| return |
| } |
| // Replace the symbolic tag with a direct one |
| tag, err = m.resolveSymbolicTag(ctx, adsField) |
| if err != nil { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrap(err, "invalid tag item type"), |
| } |
| log.Debug().Msgf("Invalid tag item type %T", tag) |
| return |
| } |
| } |
| directAdsTag, ok := tag.(*model.DirectPlcTag) |
| if !ok { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.New("invalid tag item type"), |
| } |
| log.Debug().Msgf("Invalid tag item type %T", tag) |
| return |
| } |
| |
| directAdsTags[tagName] = directAdsTag |
| |
| // Size of one element. |
| size := directAdsTag.DataType.GetSize() |
| |
| // Calculate how many elements in total we'll be reading. |
| arraySize := uint32(1) |
| if len(tag.GetArrayInfo()) > 0 { |
| for _, arrayInfo := range tag.GetArrayInfo() { |
| arraySize = arraySize * arrayInfo.GetSize() |
| } |
| } |
| |
| // Status code + payload size |
| expectedTagSize := 4 + (size * arraySize) |
| expectedResponseDataSize += expectedTagSize |
| |
| requestItems = append(requestItems, driverModel.NewAdsMultiRequestItemRead(directAdsTag.IndexGroup, directAdsTag.IndexOffset, size*arraySize)) |
| } |
| |
| response, err := m.ExecuteAdsReadWriteRequest(ctx, uint32(driverModel.ReservedIndexGroups_ADSIGRP_MULTIPLE_READ), uint32(len(directAdsTags)), expectedResponseDataSize, requestItems, nil) |
| if err != nil { |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrap(err, "error executing multi-item read request"), |
| } |
| return |
| } |
| |
| rb := utils.NewReadBufferByteBased(response.GetData(), utils.WithByteOrderForReadBufferByteBased(binary.LittleEndian)) |
| |
| // Read in the response codes first. |
| responseCodes := map[string]apiModel.PlcResponseCode{} |
| plcValues := map[string]values.PlcValue{} |
| for _, tagName := range readRequest.GetTagNames() { |
| returnCodeValue, err := rb.ReadUint32("returnCode", 32) |
| if err != nil { |
| responseCodes[tagName] = apiModel.PlcResponseCode_INTERNAL_ERROR |
| } else if returnCodeValue != 0x00000000 { |
| // TODO: Correctly handle this. |
| responseCodes[tagName] = apiModel.PlcResponseCode_REMOTE_ERROR |
| } else { |
| responseCodes[tagName] = apiModel.PlcResponseCode_OK |
| } |
| } |
| |
| // Parse the plc values for those items that were ok. |
| for _, tagName := range readRequest.GetTagNames() { |
| if responseCodes[tagName] != apiModel.PlcResponseCode_OK { |
| continue |
| } |
| |
| directAdsTag := directAdsTags[tagName] |
| log.Debug().Msgf("get a tag from request with name %s", tagName) |
| // Try to parse the value |
| plcValue, err := m.parsePlcValue(directAdsTag.DataType, directAdsTag.DataType.GetArrayInfo(), rb) |
| if err != nil { |
| log.Error().Err(err).Msg("Error parsing plc value") |
| responseCodes[tagName] = apiModel.PlcResponseCode_INTERNAL_ERROR |
| } else { |
| plcValues[tagName] = plcValue |
| responseCodes[tagName] = apiModel.PlcResponseCode_OK |
| } |
| } |
| |
| // Return the response to the caller. |
| result <- &internalModel.DefaultPlcReadRequestResult{ |
| Request: readRequest, |
| Response: internalModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues), |
| Err: nil, |
| } |
| } |
| |
| func (m *Connection) parsePlcValue(dataType driverModel.AdsDataTypeTableEntry, arrayInfo []driverModel.AdsDataTypeArrayInfo, rb utils.ReadBufferByteBased) (values.PlcValue, error) { |
| // Decode the data according to the information from the request |
| // Based on the AdsDataTypeTableEntry in tag.DataType() parse the data |
| if len(arrayInfo) > 0 { |
| // This is an Array/List type. |
| curArrayInfo := arrayInfo[0] |
| arrayItemTypeName := dataType.GetDataTypeName()[strings.Index(dataType.GetDataTypeName(), " OF ")+4:] |
| arrayItemType, ok := m.driverContext.dataTypeTable[arrayItemTypeName] |
| if !ok { |
| return nil, fmt.Errorf("couldn't resolve array item type %s", arrayItemTypeName) |
| } |
| var plcValues []values.PlcValue |
| for i := uint32(0); i < curArrayInfo.GetNumElements(); i++ { |
| restArrayInfo := arrayInfo[1:] |
| plcValue, err := m.parsePlcValue(arrayItemType, restArrayInfo, rb) |
| if err != nil { |
| return nil, errors.Wrap(err, "error decoding list item") |
| } |
| plcValues = append(plcValues, plcValue) |
| } |
| return internalValues.NewPlcList(plcValues), nil |
| } else if len(dataType.GetChildren()) > 0 { |
| // This is a Struct type. |
| plcValues := map[string]values.PlcValue{} |
| startPos := uint32(rb.GetPos()) |
| curPos := uint32(0) |
| for _, child := range dataType.GetChildren() { |
| childName := child.GetPropertyName() |
| childDataType, ok := m.driverContext.dataTypeTable[child.GetDataTypeName()] |
| if !ok { |
| return nil, fmt.Errorf("couldn't find data type named %s for property %s of type %s", child.GetDataTypeName(), childName, dataType.GetDataTypeName()) |
| } |
| if child.GetOffset() > curPos { |
| skipBytes := child.GetOffset() - curPos |
| for i := uint32(0); i < skipBytes; i++ { |
| _, _ = rb.ReadByte("") |
| } |
| } |
| childValue, err := m.parsePlcValue(childDataType, childDataType.GetArrayInfo(), rb) |
| if err != nil { |
| return nil, errors.Wrap(err, fmt.Sprintf("error parsing propery %s of type %s", childName, dataType.GetDataTypeName())) |
| } |
| plcValues[childName] = childValue |
| curPos = uint32(rb.GetPos()) - startPos |
| } |
| return internalValues.NewPlcStruct(plcValues), nil |
| } else { |
| // This is a primitive type. |
| valueType, stringLength := m.getPlcValueForAdsDataTypeTableEntry(dataType) |
| if valueType == values.NULL { |
| return nil, errors.New(fmt.Sprintf("error converting %s into plc4x plc-value type", dataType.GetDataTypeName())) |
| } |
| adsValueType, ok := driverModel.PlcValueTypeByName(valueType.String()) |
| if !ok { |
| return nil, errors.New(fmt.Sprintf("error converting plc4x plc-value type %s into ads plc-value type", valueType.String())) |
| } |
| return driverModel.DataItemParseWithBuffer(context.Background(), rb, adsValueType, stringLength) |
| } |
| } |