You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2021/03/29 14:07:28 UTC
[plc4x] branch develop updated: plc4go: first ads driver draft +
contains additional minor tweaks
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/develop by this push:
new 37fb82c plc4go: first ads driver draft + contains additional minor tweaks
37fb82c is described below
commit 37fb82cde0728923282628118adb47d9f54d190c
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Mar 29 16:07:11 2021 +0200
plc4go: first ads driver draft
+ contains additional minor tweaks
---
plc4go/internal/plc4go/ads/Connection.go | 157 +++++++++++++++
plc4go/internal/plc4go/ads/Driver.go | 49 ++++-
plc4go/internal/plc4go/ads/Field.go | 113 +++++++++++
plc4go/internal/plc4go/ads/FieldHandler.go | 134 ++++++++++++-
plc4go/internal/plc4go/ads/MessageCodec.go | 217 +++++++++++++++++++++
plc4go/internal/plc4go/ads/Reader.go | 217 +++++++++++++++++++++
.../ads/{FieldHandler.go => ValueHandler.go} | 16 +-
plc4go/internal/plc4go/ads/Writer.go | 200 +++++++++++++++++++
plc4go/internal/plc4go/ads/fieldtype_string.go | 54 +++++
plc4go/internal/plc4go/modbus/FieldHandler.go | 4 +-
.../plc4go/spi/testutils/DriverTestRunner.go | 50 ++++-
plc4go/pkg/plc4go/driverManager.go | 10 +-
plc4go/pom.xml | 16 ++
13 files changed, 1203 insertions(+), 34 deletions(-)
diff --git a/plc4go/internal/plc4go/ads/Connection.go b/plc4go/internal/plc4go/ads/Connection.go
new file mode 100644
index 0000000..4ffefcc
--- /dev/null
+++ b/plc4go/internal/plc4go/ads/Connection.go
@@ -0,0 +1,157 @@
+//
+// 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
+//
+// http://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 (
+ "fmt"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/interceptors"
+ internalModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go"
+ apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/rs/zerolog/log"
+ "time"
+)
+
+type ConnectionMetadata struct {
+}
+
+func (m ConnectionMetadata) GetConnectionAttributes() map[string]string {
+ return map[string]string{}
+}
+
+func (m ConnectionMetadata) CanRead() bool {
+ return true
+}
+
+func (m ConnectionMetadata) CanWrite() bool {
+ return true
+}
+
+func (m ConnectionMetadata) CanSubscribe() bool {
+ return true
+}
+
+func (m ConnectionMetadata) CanBrowse() bool {
+ return false
+}
+
+// TODO: maybe we can use a DefaultConnection struct here with delegates
+type Connection struct {
+ messageCodec spi.MessageCodec
+ options map[string][]string
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ requestInterceptor internalModel.RequestInterceptor
+}
+
+func NewConnection(messageCodec spi.MessageCodec, options map[string][]string, fieldHandler spi.PlcFieldHandler) Connection {
+ return Connection{
+ messageCodec: messageCodec,
+ options: options,
+ fieldHandler: fieldHandler,
+ valueHandler: NewValueHandler(),
+ requestInterceptor: interceptors.NewSingleItemRequestInterceptor(),
+ }
+}
+
+func (m Connection) Connect() <-chan plc4go.PlcConnectionConnectResult {
+ log.Trace().Msg("Connecting")
+ ch := make(chan plc4go.PlcConnectionConnectResult)
+ go func() {
+ err := m.messageCodec.Connect()
+ ch <- plc4go.NewPlcConnectionConnectResult(m, err)
+ }()
+ return ch
+}
+
+func (m Connection) BlockingClose() {
+ log.Trace().Msg("Closing blocked")
+ closeResults := m.Close()
+ select {
+ case <-closeResults:
+ return
+ case <-time.After(time.Second * 5):
+ return
+ }
+}
+
+func (m Connection) Close() <-chan plc4go.PlcConnectionCloseResult {
+ log.Trace().Msg("Close")
+ // TODO: Implement ...
+ ch := make(chan plc4go.PlcConnectionCloseResult)
+ go func() {
+ ch <- plc4go.NewPlcConnectionCloseResult(m, nil)
+ }()
+ return ch
+}
+
+func (m Connection) IsConnected() bool {
+ panic("implement me")
+}
+
+func (m Connection) Ping() <-chan plc4go.PlcConnectionPingResult {
+ panic("implement me")
+}
+
+func (m Connection) GetMetadata() apiModel.PlcConnectionMetadata {
+ return ConnectionMetadata{}
+}
+
+func (m Connection) ReadRequestBuilder() apiModel.PlcReadRequestBuilder {
+ return internalModel.NewDefaultPlcReadRequestBuilderWithInterceptor(m.fieldHandler,
+ NewReader(m.messageCodec), m.requestInterceptor)
+}
+
+func (m Connection) WriteRequestBuilder() apiModel.PlcWriteRequestBuilder {
+ return internalModel.NewDefaultPlcWriteRequestBuilder(
+ m.fieldHandler, m.valueHandler, NewWriter(m.messageCodec))
+}
+
+func (m Connection) SubscriptionRequestBuilder() apiModel.PlcSubscriptionRequestBuilder {
+ panic("implement me")
+}
+
+func (m Connection) UnsubscriptionRequestBuilder() apiModel.PlcUnsubscriptionRequestBuilder {
+ panic("implement me")
+}
+
+func (m Connection) BrowseRequestBuilder() apiModel.PlcBrowseRequestBuilder {
+ panic("implement me")
+}
+
+func (m Connection) GetTransportInstance() transports.TransportInstance {
+ if mc, ok := m.messageCodec.(spi.TransportInstanceExposer); ok {
+ return mc.GetTransportInstance()
+ }
+ return nil
+}
+
+func (m Connection) GetPlcFieldHandler() spi.PlcFieldHandler {
+ return m.fieldHandler
+}
+
+func (m Connection) GetPlcValueHandler() spi.PlcValueHandler {
+ return m.valueHandler
+}
+
+func (m Connection) String() string {
+ return fmt.Sprintf("ads.Connection{}")
+}
diff --git a/plc4go/internal/plc4go/ads/Driver.go b/plc4go/internal/plc4go/ads/Driver.go
index a5f566b..b13bdb4 100644
--- a/plc4go/internal/plc4go/ads/Driver.go
+++ b/plc4go/internal/plc4go/ads/Driver.go
@@ -20,12 +20,16 @@ package ads
import (
"github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
"github.com/apache/plc4x/plc4go/pkg/plc4go"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+ "net/url"
)
type Driver struct {
fieldHandler spi.PlcFieldHandler
- plc4go.PlcDriver
}
func NewDriver() plc4go.PlcDriver {
@@ -42,6 +46,49 @@ func (m Driver) GetProtocolName() string {
return "Beckhoff TwinCat ADS"
}
+func (m Driver) GetDefaultTransport() string {
+ panic("implement me")
+}
+
+func (m Driver) CheckQuery(query string) error {
+ panic("implement me")
+}
+
+func (m Driver) GetConnection(transportUrl url.URL, transports map[string]transports.Transport, options map[string][]string) <-chan plc4go.PlcConnectionConnectResult {
+ log.Debug().Stringer("transportUrl", &transportUrl).Msgf("Get connection for transport url with %d transport(s) and %d option(s)", len(transports), len(options))
+ // Get an the transport specified in the url
+ transport, ok := transports[transportUrl.Scheme]
+ if !ok {
+ log.Error().Stringer("transportUrl", &transportUrl).Msgf("We couldn't find a transport for scheme %s", transportUrl.Scheme)
+ ch := make(chan plc4go.PlcConnectionConnectResult)
+ ch <- plc4go.NewPlcConnectionConnectResult(nil, errors.Errorf("couldn't find transport for given transport url %#v", transportUrl))
+ return ch
+ }
+ // Provide a default-port to the transport, which is used, if the user doesn't provide on in the connection string.
+ options["defaultTcpPort"] = []string{"48898"}
+ // Have the transport create a new transport-instance.
+ transportInstance, err := transport.CreateTransportInstance(transportUrl, options)
+ if err != nil {
+ log.Error().Stringer("transportUrl", &transportUrl).Msgf("We couldn't create a transport instance for port %#v", options["defaultTcpPort"])
+ ch := make(chan plc4go.PlcConnectionConnectResult)
+ ch <- plc4go.NewPlcConnectionConnectResult(nil, errors.New("couldn't initialize transport configuration for given transport url "+transportUrl.String()))
+ return ch
+ }
+
+ // Create a new codec for taking care of encoding/decoding of messages
+ codec := NewMessageCodec(transportInstance, nil)
+ log.Debug().Msgf("working with codec %#v", codec)
+
+ // Create the new connection
+ connection := NewConnection(codec, options, m.fieldHandler)
+ log.Info().Stringer("connection", connection).Msg("created connection, connecting now")
+ return connection.Connect()
+}
+
+func (m Driver) Discover(_ func(event model.PlcDiscoveryEvent)) error {
+ panic("not available")
+}
+
func (m Driver) SupportsDiscovery() bool {
return false
}
diff --git a/plc4go/internal/plc4go/ads/Field.go b/plc4go/internal/plc4go/ads/Field.go
new file mode 100644
index 0000000..21241ec
--- /dev/null
+++ b/plc4go/internal/plc4go/ads/Field.go
@@ -0,0 +1,113 @@
+//
+// 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
+//
+// http://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 (
+ "encoding/xml"
+ "fmt"
+ model2 "github.com/apache/plc4x/plc4go/internal/plc4go/ads/readwrite/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+)
+
+type PlcField struct {
+ FieldType FieldType
+ IndexGroup uint32
+ IndexOffset uint32
+ SymbolicAddress string
+ StringLength int32
+ NumberOfElements int64
+ Datatype model2.AdsDataType
+}
+
+func (m PlcField) GetAddressString() string {
+ return fmt.Sprintf("%dx%05d%05d%s%05d%05d:%s", m.FieldType, m.IndexGroup, m.IndexOffset, m.SymbolicAddress, m.StringLength, m.NumberOfElements, m.Datatype.String())
+}
+
+func (m PlcField) GetTypeName() string {
+ return m.FieldType.GetName()
+}
+
+func (m PlcField) GetQuantity() uint16 {
+ return 1
+}
+
+func NewAdsPlcField(fieldType FieldType, indexGroup uint32, indexOffset uint32, adsDataType model2.AdsDataType, stringLength int32, numberOfElements int64) (model.PlcField, error) {
+ return PlcField{
+ FieldType: fieldType,
+ IndexGroup: indexGroup,
+ IndexOffset: indexOffset,
+ SymbolicAddress: "",
+ StringLength: stringLength,
+ NumberOfElements: numberOfElements,
+ Datatype: adsDataType,
+ }, nil
+}
+
+func NewAdsSymbolicPlcField(fieldType FieldType, symbolicAddress string, adsDataType model2.AdsDataType, stringLength int32, numberOfElements int64) (model.PlcField, error) {
+ return PlcField{
+ FieldType: fieldType,
+ IndexGroup: 0,
+ IndexOffset: 0,
+ SymbolicAddress: symbolicAddress,
+ StringLength: stringLength,
+ NumberOfElements: numberOfElements,
+ Datatype: adsDataType,
+ }, nil
+}
+
+func CastToAdsFieldFromPlcField(plcField model.PlcField) (PlcField, error) {
+ if adsField, ok := plcField.(PlcField); ok {
+ return adsField, nil
+ }
+ return PlcField{}, errors.New("couldn't cast to AdsPlcField")
+}
+
+func (m PlcField) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ log.Trace().Msg("MarshalXML")
+ if err := e.EncodeToken(xml.StartElement{Name: xml.Name{Local: m.FieldType.GetName()}}); err != nil {
+ return err
+ }
+
+ if err := e.EncodeElement(m.IndexGroup, xml.StartElement{Name: xml.Name{Local: "indexGroup"}}); err != nil {
+ return err
+ }
+ if err := e.EncodeElement(m.IndexOffset, xml.StartElement{Name: xml.Name{Local: "indexOffset"}}); err != nil {
+ return err
+ }
+ if err := e.EncodeElement(m.SymbolicAddress, xml.StartElement{Name: xml.Name{Local: "symbolicAddress"}}); err != nil {
+ return err
+ }
+ if err := e.EncodeElement(m.StringLength, xml.StartElement{Name: xml.Name{Local: "stringLength"}}); err != nil {
+ return err
+ }
+ if err := e.EncodeElement(m.NumberOfElements, xml.StartElement{Name: xml.Name{Local: "numberOfElements"}}); err != nil {
+ return err
+ }
+ if err := e.EncodeElement(m.Datatype.String(), xml.StartElement{Name: xml.Name{Local: "dataType"}}); err != nil {
+ return err
+ }
+
+ if err := e.EncodeToken(xml.EndElement{Name: xml.Name{Local: m.FieldType.GetName()}}); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/plc4go/internal/plc4go/ads/FieldHandler.go b/plc4go/internal/plc4go/ads/FieldHandler.go
index ce2a708..14e657e 100644
--- a/plc4go/internal/plc4go/ads/FieldHandler.go
+++ b/plc4go/internal/plc4go/ads/FieldHandler.go
@@ -19,19 +19,143 @@
package ads
import (
- "errors"
- "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ model2 "github.com/apache/plc4x/plc4go/internal/plc4go/ads/readwrite/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+ "regexp"
+ "strconv"
)
+type FieldType uint8
+
+//go:generate stringer -type FieldType
+const (
+ StringField FieldType = 0x00
+ Field FieldType = 0x01
+ SymbolicStringField FieldType = 0x03
+ SymbolicField FieldType = 0x04
+)
+
+func (i FieldType) GetName() string {
+ return fmt.Sprintf("AdsField%s", i.String())
+}
+
type FieldHandler struct {
- spi.PlcFieldHandler
+ directAdsStringField *regexp.Regexp
+ directAdsField *regexp.Regexp
+ symbolicAdsStringField *regexp.Regexp
+ symbolicAdsField *regexp.Regexp
}
func NewFieldHandler() FieldHandler {
- return FieldHandler{}
+ return FieldHandler{
+ directAdsStringField: regexp.MustCompile("^((0[xX](?P<indexGroupHex>[0-9a-fA-F]+))|(?P<indexGroup>\\d+))/((0[xX](?P<indexOffsetHex>[0-9a-fA-F]+))|(?P<indexOffset>\\d+)):(?P<adsDataType>STRING|WSTRING)\\((?P<stringLength>\\d{1,3})\\)(\\[(?P<numberOfElements>\\d+)])?"),
+ directAdsField: regexp.MustCompile("^((0[xX](?P<indexGroupHex>[0-9a-fA-F]+))|(?P<indexGroup>\\d+))/((0[xX](?P<indexOffsetHex>[0-9a-fA-F]+))|(?P<indexOffset>\\d+)):(?P<adsDataType>\\w+)(\\[(?P<numberOfElements>\\d+)])?"),
+ symbolicAdsStringField: regexp.MustCompile("^(?P<symbolicAddress>.+):(?P<adsDataType>'STRING'|'WSTRING')\\((?P<stringLength>\\d{1,3})\\)(\\[(?P<numberOfElements>\\d+)])?"),
+ symbolicAdsField: regexp.MustCompile("^(?P<symbolicAddress>.+):(?P<adsDataType>\\w+)(\\[(?P<numberOfElements>\\d+)])?"),
+ }
}
func (m FieldHandler) ParseQuery(query string) (apiModel.PlcField, error) {
- return nil, errors.New("Invalid address format for address '" + query + "'")
+ if match := utils.GetSubgroupMatches(m.directAdsStringField, query); match != nil {
+ var indexGroup uint32
+ if indexGroupHexString := match["indexGroupHex"]; indexGroupHexString != "" {
+ decodeString, err := hex.DecodeString(indexGroupHexString[2:])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexGroup = binary.BigEndian.Uint32(decodeString)
+ } else {
+ parsedIndexGroup, err := strconv.Atoi(match["indexGroup"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexGroup = uint32(parsedIndexGroup)
+ }
+ var indexOffset uint32
+ if indexOffsetHexString := match["indexOffsetHex"]; indexOffsetHexString != "" {
+ decodeString, err := hex.DecodeString(indexOffsetHexString[2:])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexOffset = binary.BigEndian.Uint32(decodeString)
+ } else {
+ parsedIndexOffset, err := strconv.Atoi(match["indexOffset"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexOffset = uint32(parsedIndexOffset)
+ }
+ stringLength, err := strconv.Atoi(match["stringLength"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding string length")
+ }
+ numberOfElements, err := strconv.Atoi(match["numberOfElements"])
+ if err != nil {
+ log.Trace().Msg("Falling back to number of elements 1")
+ numberOfElements = 1
+ }
+
+ return NewAdsPlcField(StringField, indexGroup, indexOffset, model2.AdsDataTypeByName(match["datatype"]), int32(stringLength), int64(numberOfElements))
+ } else if match := utils.GetSubgroupMatches(m.directAdsField, query); match != nil {
+ var indexGroup uint32
+ if indexGroupHexString := match["indexGroupHex"]; indexGroupHexString != "" {
+ decodeString, err := hex.DecodeString(indexGroupHexString[2:])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexGroup = binary.BigEndian.Uint32(decodeString)
+ } else {
+ parsedIndexGroup, err := strconv.Atoi(match["indexGroup"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexGroup = uint32(parsedIndexGroup)
+ }
+ var indexOffset uint32
+ if indexOffsetHexString := match["indexOffsetHex"]; indexOffsetHexString != "" {
+ decodeString, err := hex.DecodeString(indexOffsetHexString[2:])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexOffset = binary.BigEndian.Uint32(decodeString)
+ } else {
+ parsedIndexOffset, err := strconv.Atoi(match["indexOffset"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding index group")
+ }
+ indexOffset = uint32(parsedIndexOffset)
+ }
+
+ adsDataType := model2.AdsDataTypeByName(match["datatype"])
+ numberOfElements, err := strconv.Atoi(match["numberOfElements"])
+ if err != nil {
+ log.Trace().Msg("Falling back to number of elements 1")
+ numberOfElements = 1
+ }
+ return NewAdsPlcField(Field, indexGroup, indexOffset, adsDataType, int32(0), int64(numberOfElements))
+ } else if match := utils.GetSubgroupMatches(m.symbolicAdsStringField, query); match != nil {
+ stringLength, err := strconv.Atoi(match["stringLength"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding string length")
+ }
+ numberOfElements, err := strconv.Atoi(match["numberOfElements"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding number of elements")
+ }
+ return NewAdsSymbolicPlcField(SymbolicStringField, match["symbolicAddress"], model2.AdsDataTypeByName(match["datatype"]), int32(stringLength), int64(numberOfElements))
+ } else if match := utils.GetSubgroupMatches(m.symbolicAdsStringField, query); match != nil {
+ numberOfElements, err := strconv.Atoi(match["numberOfElements"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error decoding number of elements")
+ }
+ return NewAdsSymbolicPlcField(SymbolicField, match["symbolicAddress"], model2.AdsDataTypeByName(match["datatype"]), int32(0), int64(numberOfElements))
+ } else {
+ return nil, errors.Errorf("Invalid address format for address '%s'", query)
+ }
}
diff --git a/plc4go/internal/plc4go/ads/MessageCodec.go b/plc4go/internal/plc4go/ads/MessageCodec.go
new file mode 100644
index 0000000..6da826f
--- /dev/null
+++ b/plc4go/internal/plc4go/ads/MessageCodec.go
@@ -0,0 +1,217 @@
+//
+// 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
+//
+// http://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 (
+ "fmt"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/ads/readwrite/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+ "time"
+)
+
+type Expectation struct {
+ expiration time.Time
+ acceptsMessage spi.AcceptsMessage
+ handleMessage spi.HandleMessage
+ handleError spi.HandleError
+}
+
+func (m Expectation) String() string {
+ return fmt.Sprintf("Expectation(expires at %v)", m.expiration)
+}
+
+type MessageCodec struct {
+ expectationCounter int32
+ transportInstance transports.TransportInstance
+ defaultIncomingMessageChannel chan interface{}
+ expectations []Expectation
+ running bool
+}
+
+func NewMessageCodec(transportInstance transports.TransportInstance, defaultIncomingMessageChannel chan interface{}) *MessageCodec {
+ codec := &MessageCodec{
+ expectationCounter: 1,
+ transportInstance: transportInstance,
+ defaultIncomingMessageChannel: defaultIncomingMessageChannel,
+ expectations: []Expectation{},
+ running: true,
+ }
+ // TODO: should we better move this go func into Connect(). If not a better explanation why we start the worker so early
+ // Start a worker that handles processing of responses
+ go work(codec)
+ return codec
+}
+
+func (m *MessageCodec) Connect() error {
+ log.Info().Msg("Connecting")
+ err := m.transportInstance.Connect()
+ if err == nil {
+ m.running = true
+ }
+ return err
+}
+
+func (m *MessageCodec) Disconnect() error {
+ log.Info().Msg("Disconnecting")
+ m.running = false
+ return m.transportInstance.Close()
+}
+
+func (m *MessageCodec) Send(message interface{}) error {
+ log.Trace().Msg("Sending message")
+ // Cast the message to the correct type of struct
+ tcpPaket := model.CastAmsTCPPacket(message)
+ // Serialize the request
+ wb := utils.NewWriteBuffer()
+ err := tcpPaket.Serialize(*wb)
+ if err != nil {
+ return errors.Wrap(err, "error serializing request")
+ }
+
+ // Send it to the PLC
+ err = m.transportInstance.Write(wb.GetBytes())
+ if err != nil {
+ return errors.Wrap(err, "error sending request")
+ }
+ return nil
+}
+
+func (m *MessageCodec) Expect(acceptsMessage spi.AcceptsMessage, handleMessage spi.HandleMessage, handleError spi.HandleError, ttl time.Duration) error {
+ expectation := Expectation{
+ expiration: time.Now().Add(ttl),
+ acceptsMessage: acceptsMessage,
+ handleMessage: handleMessage,
+ handleError: handleError,
+ }
+ m.expectations = append(m.expectations, expectation)
+ return nil
+}
+
+func (m *MessageCodec) SendRequest(message interface{}, acceptsMessage spi.AcceptsMessage, handleMessage spi.HandleMessage, handleError spi.HandleError, ttl time.Duration) error {
+ log.Trace().Msg("Sending request")
+ // Send the actual message
+ err := m.Send(message)
+ if err != nil {
+ return errors.Wrap(err, "Error sending the request")
+ }
+ return m.Expect(acceptsMessage, handleMessage, handleError, ttl)
+}
+
+func (m *MessageCodec) GetDefaultIncomingMessageChannel() chan interface{} {
+ return m.defaultIncomingMessageChannel
+}
+
+func (m *MessageCodec) receive() (interface{}, error) {
+ log.Trace().Msg("receiving")
+ // We need at least 6 bytes in order to know how big the packet is in total
+ if num, err := m.transportInstance.GetNumReadableBytes(); (err == nil) && (num >= 6) {
+ log.Debug().Msgf("we got %d readable bytes", num)
+ data, err := m.transportInstance.PeekReadableBytes(6)
+ if err != nil {
+ log.Warn().Err(err).Msg("error peeking")
+ // TODO: Possibly clean up ...
+ return nil, nil
+ }
+ // Get the size of the entire packet
+ // TODO: pretty sure this is wrong for ADS CP from modbus
+ packetSize := (uint32(data[4]) << 8) + uint32(data[5]) + 6
+ if num >= packetSize {
+ data, err = m.transportInstance.Read(packetSize)
+ if err != nil {
+ // TODO: Possibly clean up ...
+ return nil, nil
+ }
+ rb := utils.NewReadBuffer(data)
+ tcpPacket, err := model.AmsTCPPacketParse(rb)
+ if err != nil {
+ log.Warn().Err(err).Msg("error parsing")
+ // TODO: Possibly clean up ...
+ return nil, nil
+ }
+ return tcpPacket, nil
+ }
+ }
+ // TODO: maybe we return here a not enough error error
+ return nil, nil
+}
+
+func work(m *MessageCodec) {
+ // Start an endless loop
+ for m.running {
+ log.Trace().Msg("working")
+ if len(m.expectations) <= 0 {
+ // Sleep for 10ms
+ time.Sleep(time.Millisecond * 10)
+ continue
+ }
+ message, err := m.receive()
+ if err != nil {
+ log.Error().Err(err).Msg("got an error reading from transport")
+ continue
+ }
+ if message == nil {
+ time.Sleep(time.Millisecond * 10)
+ continue
+ }
+ now := time.Now()
+ messageHandled := false
+ // Go through all expectations
+ for index, expectation := range m.expectations {
+ // Check if this expectation has expired.
+ if now.After(expectation.expiration) {
+ log.Debug().Stringer("expectation", expectation).Msg("expired")
+ // Remove this expectation from the list.
+ m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
+ break
+ }
+
+ // Check if the current message matches the expectations
+ // If it does, let it handle the message.
+ if accepts := expectation.acceptsMessage(message); accepts {
+ log.Debug().Stringer("expectation", expectation).Msg("accepts message")
+ err = expectation.handleMessage(message)
+ if err == nil {
+ messageHandled = true
+ // Remove this expectation from the list.
+ m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
+ } else {
+ log.Error().Err(err).Msg("Error handling message")
+ }
+ break
+ }
+ }
+
+ // If the message has not been handled and a default handler is provided, call this ...
+ if !messageHandled {
+ if m.defaultIncomingMessageChannel != nil {
+ m.defaultIncomingMessageChannel <- message
+ } else {
+ log.Warn().Msgf("No handler registered for handling message %s", message)
+ }
+ }
+ }
+}
+
+func (m MessageCodec) GetTransportInstance() transports.TransportInstance {
+ return m.transportInstance
+}
diff --git a/plc4go/internal/plc4go/ads/Reader.go b/plc4go/internal/plc4go/ads/Reader.go
new file mode 100644
index 0000000..904a933
--- /dev/null
+++ b/plc4go/internal/plc4go/ads/Reader.go
@@ -0,0 +1,217 @@
+//
+// 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
+//
+// http://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 (
+ readWriteModel "github.com/apache/plc4x/plc4go/internal/plc4go/ads/readwrite/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ plc4goModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+ "math"
+ "sync/atomic"
+ "time"
+)
+
+type Reader struct {
+ transactionIdentifier uint32
+ messageCodec spi.MessageCodec
+}
+
+func NewReader(messageCodec spi.MessageCodec) *Reader {
+ return &Reader{
+ transactionIdentifier: 0,
+ messageCodec: messageCodec,
+ }
+}
+
+func (m *Reader) Read(readRequest model.PlcReadRequest) <-chan model.PlcReadRequestResult {
+ log.Trace().Msg("Reading")
+ result := make(chan model.PlcReadRequestResult)
+ go func() {
+ if len(readRequest.GetFieldNames()) != 1 {
+ result <- model.PlcReadRequestResult{
+ Request: readRequest,
+ Response: nil,
+ Err: errors.New("ads only supports single-item requests"),
+ }
+ log.Debug().Msgf("ads only supports single-item requests. Got %d fields", len(readRequest.GetFieldNames()))
+ return
+ }
+ // If we are requesting only one field, use a
+ fieldName := readRequest.GetFieldNames()[0]
+ field := readRequest.GetField(fieldName)
+ adsField, err := CastToAdsFieldFromPlcField(field)
+ if err != nil {
+ result <- model.PlcReadRequestResult{
+ Request: readRequest,
+ Response: nil,
+ Err: errors.Wrap(err, "invalid field item type"),
+ }
+ log.Debug().Msgf("Invalid field item type %T", field)
+ return
+ }
+ // TODO: move TargetAmsNetId, TargetAmsPort, SourceAmsNetId, SourceAmsPort to Connection
+ userdata := readWriteModel.AmsPacket{
+ TargetAmsNetId: &readWriteModel.AmsNetId{
+ Octet1: 0,
+ Octet2: 0,
+ Octet3: 0,
+ Octet4: 0,
+ Octet5: 0,
+ Octet6: 0,
+ },
+ TargetAmsPort: 0,
+ SourceAmsNetId: &readWriteModel.AmsNetId{
+ Octet1: 0,
+ Octet2: 0,
+ Octet3: 0,
+ Octet4: 0,
+ Octet5: 0,
+ Octet6: 0,
+ },
+ SourceAmsPort: 0,
+ CommandId: readWriteModel.CommandId_ADS_READ,
+ State: readWriteModel.NewState(false, false, false, false, false, true, false, false, false),
+ ErrorCode: 0,
+ InvokeId: 0,
+ Data: nil,
+ }
+ switch adsField.FieldType {
+ case StringField:
+ // TODO: what is our read length?
+ userdata.Data = readWriteModel.NewAdsReadRequest(adsField.IndexGroup, adsField.IndexOffset, 1)
+ case Field:
+ // TODO: what is our read length?
+ userdata.Data = readWriteModel.NewAdsReadRequest(adsField.IndexGroup, adsField.IndexOffset, 1)
+ case SymbolicStringField:
+ panic("implement me")
+ case SymbolicField:
+ panic("implement me")
+ default:
+ result <- model.PlcReadRequestResult{
+ Request: readRequest,
+ Response: nil,
+ Err: errors.Errorf("unsupported field type %x", adsField.FieldType),
+ }
+ log.Debug().Msgf("Unsupported field type %x", adsField.FieldType)
+ return
+ }
+
+ // Calculate a new transaction identifier
+ transactionIdentifier := atomic.AddUint32(&m.transactionIdentifier, 1)
+ if transactionIdentifier > math.MaxUint8 {
+ transactionIdentifier = 1
+ atomic.StoreUint32(&m.transactionIdentifier, 1)
+ }
+ log.Debug().Msgf("Calculated transaction identifier %x", transactionIdentifier)
+ userdata.InvokeId = transactionIdentifier
+
+ // Assemble the finished tcp paket
+ log.Trace().Msg("Assemble tcp paket")
+ amsTcpPaket := readWriteModel.AmsTCPPacket{
+ Userdata: &userdata,
+ }
+
+ // Send the TCP Paket over the wire
+ log.Trace().Msg("Send TCP Paket")
+ if err = m.messageCodec.SendRequest(
+ amsTcpPaket,
+ func(message interface{}) bool {
+ paket := readWriteModel.CastAmsTCPPacket(message)
+ return paket.Userdata.InvokeId == transactionIdentifier
+ },
+ func(message interface{}) error {
+ // Convert the response into an ADU
+ log.Trace().Msg("convert response to ADU")
+ amsTcpPaket := readWriteModel.CastAmsTCPPacket(message)
+ // Convert the ads response into a PLC4X response
+ log.Trace().Msg("convert response to PLC4X response")
+ readResponse, err := m.ToPlc4xReadResponse(*amsTcpPaket, readRequest)
+
+ if err != nil {
+ result <- model.PlcReadRequestResult{
+ Request: readRequest,
+ Err: errors.Wrap(err, "Error decoding response"),
+ }
+ // TODO: should we return the error here?
+ return nil
+ }
+ result <- model.PlcReadRequestResult{
+ Request: readRequest,
+ Response: readResponse,
+ }
+ return nil
+ },
+ func(err error) error {
+ result <- model.PlcReadRequestResult{
+ Request: readRequest,
+ Err: errors.Wrap(err, "got timeout while waiting for response"),
+ }
+ return nil
+ },
+ time.Second*1); err != nil {
+ result <- model.PlcReadRequestResult{
+ Request: readRequest,
+ Response: nil,
+ Err: errors.Wrap(err, "error sending message"),
+ }
+ }
+ }()
+ return result
+}
+
+func (m *Reader) ToPlc4xReadResponse(amsTcpPaket readWriteModel.AmsTCPPacket, readRequest model.PlcReadRequest) (model.PlcReadResponse, error) {
+ var data []uint8
+ switch amsTcpPaket.Userdata.Data.Child.(type) {
+ case *readWriteModel.AdsReadResponse:
+ readResponse := readWriteModel.CastAdsReadResponse(amsTcpPaket.Userdata.Data)
+ data = utils.Int8ArrayToUint8Array(readResponse.Data)
+ // Pure Boolean ...
+ default:
+ return nil, errors.Errorf("unsupported response type %T", amsTcpPaket.Userdata.Data.Child)
+ }
+
+ // Get the field from the request
+ log.Trace().Msg("get a field from request")
+ fieldName := readRequest.GetFieldNames()[0]
+ field, err := CastToAdsFieldFromPlcField(readRequest.GetField(fieldName))
+ if err != nil {
+ return nil, errors.Wrap(err, "error casting to ads-field")
+ }
+
+ // Decode the data according to the information from the request
+ log.Trace().Msg("decode data")
+ rb := utils.NewReadBuffer(data)
+ value, err := readWriteModel.DataItemParse(rb, field.Datatype.DataFormatName(), field.StringLength)
+ if err != nil {
+ return nil, errors.Wrap(err, "Error parsing data item")
+ }
+ responseCodes := map[string]model.PlcResponseCode{}
+ plcValues := map[string]values.PlcValue{}
+ plcValues[fieldName] = value
+ responseCodes[fieldName] = model.PlcResponseCode_OK
+
+ // Return the response
+ log.Trace().Msg("Returning the response")
+ return plc4goModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues), nil
+}
diff --git a/plc4go/internal/plc4go/ads/FieldHandler.go b/plc4go/internal/plc4go/ads/ValueHandler.go
similarity index 68%
copy from plc4go/internal/plc4go/ads/FieldHandler.go
copy to plc4go/internal/plc4go/ads/ValueHandler.go
index ce2a708..5290c3e 100644
--- a/plc4go/internal/plc4go/ads/FieldHandler.go
+++ b/plc4go/internal/plc4go/ads/ValueHandler.go
@@ -19,19 +19,13 @@
package ads
import (
- "errors"
- "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
- apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values"
)
-type FieldHandler struct {
- spi.PlcFieldHandler
+type ValueHandler struct {
+ values.IEC61131ValueHandler
}
-func NewFieldHandler() FieldHandler {
- return FieldHandler{}
-}
-
-func (m FieldHandler) ParseQuery(query string) (apiModel.PlcField, error) {
- return nil, errors.New("Invalid address format for address '" + query + "'")
+func NewValueHandler() ValueHandler {
+ return ValueHandler{}
}
diff --git a/plc4go/internal/plc4go/ads/Writer.go b/plc4go/internal/plc4go/ads/Writer.go
new file mode 100644
index 0000000..1228370
--- /dev/null
+++ b/plc4go/internal/plc4go/ads/Writer.go
@@ -0,0 +1,200 @@
+//
+// 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
+//
+// http://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 (
+ readWriteModel "github.com/apache/plc4x/plc4go/internal/plc4go/ads/readwrite/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ plc4goModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+ "math"
+ "sync/atomic"
+ "time"
+)
+
+type Writer struct {
+ transactionIdentifier uint32
+ messageCodec spi.MessageCodec
+}
+
+func NewWriter(messageCodec spi.MessageCodec) Writer {
+ return Writer{
+ transactionIdentifier: 0,
+ messageCodec: messageCodec,
+ }
+}
+
+func (m Writer) Write(writeRequest model.PlcWriteRequest) <-chan model.PlcWriteRequestResult {
+ result := make(chan model.PlcWriteRequestResult)
+ go func() {
+ // If we are requesting only one field, use a
+ if len(writeRequest.GetFieldNames()) != 1 {
+ result <- model.PlcWriteRequestResult{
+ Request: writeRequest,
+ Response: nil,
+ Err: errors.New("ads only supports single-item requests"),
+ }
+ return
+ }
+ fieldName := writeRequest.GetFieldNames()[0]
+
+ // Get the ads field instance from the request
+ field := writeRequest.GetField(fieldName)
+ adsField, err := CastToAdsFieldFromPlcField(field)
+ if err != nil {
+ result <- model.PlcWriteRequestResult{
+ Request: writeRequest,
+ Response: nil,
+ Err: errors.Wrap(err, "invalid field item type"),
+ }
+ return
+ }
+
+ // Get the value from the request and serialize it to a byte array
+ value := writeRequest.GetValue(fieldName)
+ io := utils.NewWriteBuffer()
+ if err := readWriteModel.DataItemSerialize(io, value, adsField.Datatype.DataFormatName(), adsField.StringLength); err != nil {
+ result <- model.PlcWriteRequestResult{
+ Request: writeRequest,
+ Response: nil,
+ Err: errors.Wrap(err, "error serializing value"),
+ }
+ return
+ }
+ data := utils.Uint8ArrayToInt8Array(io.GetBytes())
+
+ // TODO: move TargetAmsNetId, TargetAmsPort, SourceAmsNetId, SourceAmsPort to Connection
+ userdata := readWriteModel.AmsPacket{
+ TargetAmsNetId: &readWriteModel.AmsNetId{
+ Octet1: 0,
+ Octet2: 0,
+ Octet3: 0,
+ Octet4: 0,
+ Octet5: 0,
+ Octet6: 0,
+ },
+ TargetAmsPort: 0,
+ SourceAmsNetId: &readWriteModel.AmsNetId{
+ Octet1: 0,
+ Octet2: 0,
+ Octet3: 0,
+ Octet4: 0,
+ Octet5: 0,
+ Octet6: 0,
+ },
+ SourceAmsPort: 0,
+ CommandId: readWriteModel.CommandId_ADS_READ,
+ State: readWriteModel.NewState(false, false, false, false, false, true, false, false, false),
+ ErrorCode: 0,
+ InvokeId: 0,
+ Data: nil,
+ }
+ switch adsField.FieldType {
+ case StringField:
+ // TODO: what is our read length?
+ userdata.Data = readWriteModel.NewAdsWriteRequest(adsField.IndexGroup, adsField.IndexOffset, data)
+ panic("implement me")
+ case Field:
+ panic("implement me")
+ case SymbolicStringField:
+ panic("implement me")
+ case SymbolicField:
+ panic("implement me")
+ default:
+ result <- model.PlcWriteRequestResult{
+ Request: writeRequest,
+ Response: nil,
+ Err: errors.New("unsupported field type"),
+ }
+ return
+ }
+
+ // Calculate a new unit identifier
+ transactionIdentifier := atomic.AddUint32(&m.transactionIdentifier, 1)
+ if transactionIdentifier > math.MaxUint8 {
+ transactionIdentifier = 0
+ atomic.StoreUint32(&m.transactionIdentifier, 0)
+ }
+ userdata.InvokeId = transactionIdentifier
+
+ // Assemble the finished ADU
+ log.Trace().Msg("Assemble ADU")
+ amsTcpPaket := readWriteModel.AmsTCPPacket{
+ Userdata: &userdata,
+ }
+
+ // Send the TCP Paket over the wire
+ err = m.messageCodec.SendRequest(
+ amsTcpPaket,
+ func(message interface{}) bool {
+ paket := readWriteModel.CastAmsTCPPacket(message)
+ return paket.Userdata.InvokeId == transactionIdentifier
+ },
+ func(message interface{}) error {
+ // Convert the response into an ADU
+ responseAmsTcpPaket := readWriteModel.CastAmsTCPPacket(message)
+ // Convert the ads response into a PLC4X response
+ readResponse, err := m.ToPlc4xWriteResponse(amsTcpPaket, *responseAmsTcpPaket, writeRequest)
+
+ if err != nil {
+ result <- model.PlcWriteRequestResult{
+ Request: writeRequest,
+ Err: errors.Wrap(err, "Error decoding response"),
+ }
+ } else {
+ result <- model.PlcWriteRequestResult{
+ Request: writeRequest,
+ Response: readResponse,
+ }
+ }
+ return nil
+ },
+ func(err error) error {
+ result <- model.PlcWriteRequestResult{
+ Request: writeRequest,
+ Err: errors.New("got timeout while waiting for response"),
+ }
+ return nil
+ },
+ time.Second*1)
+ }()
+ return result
+}
+
+func (m Writer) ToPlc4xWriteResponse(requestTcpPaket readWriteModel.AmsTCPPacket, responseTcpPaket readWriteModel.AmsTCPPacket, writeRequest model.PlcWriteRequest) (model.PlcWriteResponse, error) {
+ responseCodes := map[string]model.PlcResponseCode{}
+ fieldName := writeRequest.GetFieldNames()[0]
+
+ // we default to an error until its proven wrong
+ responseCodes[fieldName] = model.PlcResponseCode_INTERNAL_ERROR
+ switch responseTcpPaket.Userdata.Data.Child.(type) {
+ case *readWriteModel.AdsWriteResponse:
+ resp := readWriteModel.CastAdsWriteResponse(responseTcpPaket.Userdata.Data)
+ responseCodes[fieldName] = model.PlcResponseCode(resp.Result)
+ default:
+ return nil, errors.Errorf("unsupported response type %T", responseTcpPaket.Userdata.Data.Child)
+ }
+
+ // Return the response
+ log.Trace().Msg("Returning the response")
+ return plc4goModel.NewDefaultPlcWriteResponse(writeRequest, responseCodes), nil
+}
diff --git a/plc4go/internal/plc4go/ads/fieldtype_string.go b/plc4go/internal/plc4go/ads/fieldtype_string.go
new file mode 100644
index 0000000..86d1910
--- /dev/null
+++ b/plc4go/internal/plc4go/ads/fieldtype_string.go
@@ -0,0 +1,54 @@
+// Licensed to 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. Apache Software Foundation (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
+//
+// http://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.
+
+// Code generated by "stringer -type FieldType"; DO NOT EDIT.
+
+package ads
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[StringField-0]
+ _ = x[Field-1]
+ _ = x[SymbolicStringField-3]
+ _ = x[SymbolicField-4]
+}
+
+const (
+ _FieldType_name_0 = "StringFieldField"
+ _FieldType_name_1 = "SymbolicStringFieldSymbolicField"
+)
+
+var (
+ _FieldType_index_0 = [...]uint8{0, 11, 16}
+ _FieldType_index_1 = [...]uint8{0, 19, 32}
+)
+
+func (i FieldType) String() string {
+ switch {
+ case i <= 1:
+ return _FieldType_name_0[_FieldType_index_0[i]:_FieldType_index_0[i+1]]
+ case 3 <= i && i <= 4:
+ i -= 3
+ return _FieldType_name_1[_FieldType_index_1[i]:_FieldType_index_1[i+1]]
+ default:
+ return "FieldType(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+}
diff --git a/plc4go/internal/plc4go/modbus/FieldHandler.go b/plc4go/internal/plc4go/modbus/FieldHandler.go
index 0a7b951..abadb1f 100644
--- a/plc4go/internal/plc4go/modbus/FieldHandler.go
+++ b/plc4go/internal/plc4go/modbus/FieldHandler.go
@@ -19,11 +19,11 @@
package modbus
import (
- "errors"
"fmt"
model2 "github.com/apache/plc4x/plc4go/internal/plc4go/modbus/readwrite/model"
"github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
"github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/pkg/errors"
"regexp"
)
@@ -94,5 +94,5 @@ func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
} else if match := utils.GetSubgroupMatches(m.numericExtendedRegisterPattern, query); match != nil {
return NewModbusPlcFieldFromStrings(ExtendedRegister, match["address"], match["quantity"], model2.ModbusDataTypeByName(match["datatype"]))
}
- return nil, errors.New("Invalid address format for address '" + query + "'")
+ return nil, errors.Errorf("Invalid address format for address '%s'", query)
}
diff --git a/plc4go/internal/plc4go/spi/testutils/DriverTestRunner.go b/plc4go/internal/plc4go/spi/testutils/DriverTestRunner.go
index d304ac1..e4ff029 100644
--- a/plc4go/internal/plc4go/spi/testutils/DriverTestRunner.go
+++ b/plc4go/internal/plc4go/spi/testutils/DriverTestRunner.go
@@ -22,7 +22,8 @@ import (
"encoding/hex"
"encoding/xml"
"fmt"
- model2 "github.com/apache/plc4x/plc4go/internal/plc4go/modbus/readwrite"
+ adsModel "github.com/apache/plc4x/plc4go/internal/plc4go/ads/readwrite"
+ modbusModel "github.com/apache/plc4x/plc4go/internal/plc4go/modbus/readwrite"
"github.com/apache/plc4x/plc4go/internal/plc4go/spi"
"github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
"github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports/test"
@@ -33,6 +34,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/subchen/go-xmldom"
"os"
+ "runtime/debug"
"strconv"
"testing"
"time"
@@ -135,7 +137,7 @@ func (m DriverTestsuite) ExecuteStep(connection plc4go.PlcConnection, testcase *
}
field, err := he.GetPlcFieldHandler().ParseQuery(fieldAddress)
if err != nil {
- return errors.Wrap(err, "error parsing address: "+fieldAddress)
+ return errors.Wrapf(err, "error parsing address: %s", fieldAddress)
}
if field.GetQuantity() > 1 {
var fieldValue []string
@@ -203,9 +205,21 @@ func (m DriverTestsuite) ExecuteStep(connection plc4go.PlcConnection, testcase *
// Parse the xml into a real model
log.Trace().Msg("parsing xml")
- message, err := model2.ModbusXmlParserHelper{}.Parse(typeName, payloadString)
- if err != nil {
- return errors.Wrap(err, "error parsing xml")
+ var message interface{}
+ var err error
+ switch m.driverName {
+ case "modbus":
+ message, err = modbusModel.ModbusXmlParserHelper{}.Parse(typeName, payloadString)
+ if err != nil {
+ return errors.Wrap(err, "error parsing xml")
+ }
+ case "ads":
+ message, err = adsModel.AdsXmlParserHelper{}.Parse(typeName, payloadString)
+ if err != nil {
+ return errors.Wrap(err, "error parsing xml")
+ }
+ default:
+ return errors.Errorf("Driver name %s has not mapped parser", m.driverName)
}
// Serialize the model into bytes
@@ -270,9 +284,21 @@ func (m DriverTestsuite) ExecuteStep(connection plc4go.PlcConnection, testcase *
// Parse the xml into a real model
log.Trace().Msg("Parsing model")
- message, err := model2.ModbusXmlParserHelper{}.Parse(typeName, payloadString)
- if err != nil {
- return errors.Wrap(err, "error parsing xml")
+ var message interface{}
+ var err error
+ switch m.driverName {
+ case "modbus":
+ message, err = modbusModel.ModbusXmlParserHelper{}.Parse(typeName, payloadString)
+ if err != nil {
+ return errors.Wrap(err, "error parsing xml")
+ }
+ case "ads":
+ message, err = adsModel.AdsXmlParserHelper{}.Parse(typeName, payloadString)
+ if err != nil {
+ return errors.Wrap(err, "error parsing xml")
+ }
+ default:
+ return errors.Errorf("Driver name %s has not mapped parser", m.driverName)
}
// Serialize the model into bytes
@@ -395,6 +421,12 @@ func RunDriverTestsuite(t *testing.T, driver plc4go.PlcDriver, testPath string,
for _, testcase := range testsuite.testcases {
t.Run(testcase.name, func(t *testing.T) {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Error().Msgf("\n\n-------------------------------------------------------\nFatal Failure\n%+v\n%s\n-------------------------------------------------------\n", err, debug.Stack())
+ t.FailNow()
+ }
+ }()
if skippedTestCasesMap[testcase.name] {
log.Warn().Msgf("Testcase %s skipped", testcase.name)
t.Skipf("Testcase %s skipped", testcase.name)
@@ -403,7 +435,7 @@ func RunDriverTestsuite(t *testing.T, driver plc4go.PlcDriver, testPath string,
log.Info().Msgf("Running testcase %s", testcase.name)
err := testsuite.Run(driverManager, testcase)
if err != nil {
- log.Err(err).Msgf("\n\n-------------------------------------------------------\nFailure\n%s\n-------------------------------------------------------\n", err.Error())
+ log.Error().Err(err).Msgf("\n\n-------------------------------------------------------\nFailure\n%+v\n-------------------------------------------------------\n", err)
t.Fail()
}
})
diff --git a/plc4go/pkg/plc4go/driverManager.go b/plc4go/pkg/plc4go/driverManager.go
index 2ca314e..45e90ad 100644
--- a/plc4go/pkg/plc4go/driverManager.go
+++ b/plc4go/pkg/plc4go/driverManager.go
@@ -121,13 +121,12 @@ func (m PlcDriverManger) ListTransportNames() []string {
return transportNames
}
-func (m PlcDriverManger) GetTransport(transportName string, connectionString string, options map[string][]string) (transports.Transport, error) {
- // TODO: what are the parameters above for? If not needed we can blank ("_") them
+func (m PlcDriverManger) GetTransport(transportName string, _ string, _ map[string][]string) (transports.Transport, error) {
if val, ok := m.transports[transportName]; ok {
- log.Debug().Str("transportName", transportName).Msg("Returning transport name")
+ log.Debug().Str("transportName", transportName).Msg("Returning transport")
return val, nil
}
- return nil, errors.New("couldn't find transport " + transportName)
+ return nil, errors.Errorf("couldn't find transport %s", transportName)
}
func (m PlcDriverManger) GetConnection(connectionString string) <-chan PlcConnectionConnectResult {
@@ -213,8 +212,7 @@ func (m PlcDriverManger) Discover(callback func(event model.PlcDiscoveryEvent))
if driver.SupportsDiscovery() {
err := driver.Discover(callback)
if err != nil {
- return errors.New("Error running Discover on driver " + driver.GetProtocolName() +
- ". Got error: " + err.Error())
+ return errors.Wrapf(err, "Error running Discover on driver %s", driver.GetProtocolName())
}
}
}
diff --git a/plc4go/pom.xml b/plc4go/pom.xml
index c21fe7a..13f2b7e 100644
--- a/plc4go/pom.xml
+++ b/plc4go/pom.xml
@@ -392,6 +392,22 @@
<flag>Apache Software Foundation (ASF)</flag>
</buildFlags>
<packages>
+ <package>internal/plc4go/ads/fieldtype_string.go</package>
+ </packages>
+ </configuration>
+ </execution>
+ <execution>
+ <id>add-license3</id>
+ <goals>
+ <goal>custom</goal>
+ </goals>
+ <configuration>
+ <exec>go-licenser</exec>
+ <customCommand>-licensor</customCommand>
+ <buildFlags>
+ <flag>Apache Software Foundation (ASF)</flag>
+ </buildFlags>
+ <packages>
<package>internal/plc4go/spi/testutils/steptype_string.go</package>
</packages>
</configuration>