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 2022/08/18 10:39:23 UTC
[plc4x] 02/02: feat(plc4go/cbus): first implementation of writer support
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
commit 5fd053d923cb483895b4ae9cb46fa9b54e64d8f1
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Thu Aug 18 12:39:10 2022 +0200
feat(plc4go/cbus): first implementation of writer support
---
plc4go/internal/cbus/CBusMessageFactory.go | 206 +++++++++++++
plc4go/internal/cbus/FieldHandler.go | 478 +++++++++++++++--------------
plc4go/internal/cbus/Reader.go | 144 +++------
plc4go/internal/cbus/Writer.go | 110 ++++++-
4 files changed, 609 insertions(+), 329 deletions(-)
diff --git a/plc4go/internal/cbus/CBusMessageFactory.go b/plc4go/internal/cbus/CBusMessageFactory.go
new file mode 100644
index 000000000..1b8cb1811
--- /dev/null
+++ b/plc4go/internal/cbus/CBusMessageFactory.go
@@ -0,0 +1,206 @@
+/*
+ * 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 cbus
+
+import (
+ "fmt"
+ "github.com/apache/plc4x/plc4go/pkg/api/model"
+ "github.com/apache/plc4x/plc4go/pkg/api/values"
+ readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
+ "github.com/pkg/errors"
+ "strings"
+)
+
+func FieldToCBusMessage(field model.PlcField, value values.PlcValue, alphaGenerator *AlphaGenerator, messageCodec *MessageCodec) (cBusMessage readWriteModel.CBusMessage, supportsRead, supportsWrite, supportsSubscribe bool, err error) {
+ cbusOptions := messageCodec.cbusOptions
+ requestContext := messageCodec.requestContext
+ switch field := field.(type) {
+ case *statusField:
+ var statusRequest readWriteModel.StatusRequest
+ switch field.statusRequestType {
+ case StatusRequestTypeBinaryState:
+ statusRequest = readWriteModel.NewStatusRequestBinaryState(field.application, 0x7A)
+ case StatusRequestTypeLevel:
+ statusRequest = readWriteModel.NewStatusRequestLevel(field.application, *field.startingGroupAddressLabel, 0x73)
+ }
+ command := readWriteModel.NewCBusPointToMultiPointCommandStatus(statusRequest, byte(field.application), cbusOptions)
+ header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToMultiPoint)
+ cbusCommand := readWriteModel.NewCBusCommandPointToMultiPoint(command, header, cbusOptions)
+ request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+ cBusMessage, supportsRead, supportsSubscribe = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true, true
+ return
+ case *calRecallField:
+ calData := readWriteModel.NewCALDataRecall(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandRecall, nil, requestContext)
+ //TODO: we need support for bridged commands
+ command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
+ header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+ cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
+ request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+ cBusMessage, supportsRead = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true
+ return
+ case *calIdentifyField:
+ calData := readWriteModel.NewCALDataIdentify(field.attribute, readWriteModel.CALCommandTypeContainer_CALCommandIdentify, nil, requestContext)
+ //TODO: we need support for bridged commands
+ command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
+ header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+ cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
+ request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+ cBusMessage, supportsRead = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true
+ return
+ case *calGetstatusField:
+ calData := readWriteModel.NewCALDataGetStatus(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandGetStatus, nil, requestContext)
+ //TODO: we need support for bridged commands
+ command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
+ header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+ cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
+ request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+ cBusMessage, supportsRead = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true
+ return
+ case *salField:
+ var salCommand = field.salCommand
+ if salCommand == "" {
+ return nil, false, false, false, errors.New("Empty sal command not supported")
+ }
+ var salData readWriteModel.SALData
+ switch field.application.ApplicationId() {
+ case readWriteModel.ApplicationId_FREE_USAGE:
+ panic("Not yet implemented") // TODO: implement
+ case readWriteModel.ApplicationId_TEMPERATURE_BROADCAST:
+ var temperatureBroadcastData readWriteModel.TemperatureBroadcastData
+ switch salCommand {
+ case readWriteModel.TemperatureBroadcastCommandType_BROADCAST_EVENT.PLC4XEnumName():
+ if value == nil || !value.IsList() || len(value.GetList()) != 2 || !value.GetList()[0].IsByte() || !value.GetList()[1].IsByte() {
+ return nil, false, false, false, errors.Errorf("%s requires exactly 2 arguments [temperatureGroup,temperatureByte]", salCommand)
+ }
+ commandTypeContainer := readWriteModel.TemperatureBroadcastCommandTypeContainer_TemperatureBroadcastCommandSetBroadcastEvent1_2Bytes
+ temperatureGroup := value.GetList()[0].GetByte()
+ temperatureByte := value.GetList()[1].GetByte()
+ temperatureBroadcastData = readWriteModel.NewTemperatureBroadcastData(commandTypeContainer, temperatureGroup, temperatureByte)
+ supportsWrite = true
+ default:
+ return nil, false, false, false, errors.Errorf("Unsupported command %s for %s", salCommand, field.application.ApplicationId())
+ }
+ salData = readWriteModel.NewSALDataTemperatureBroadcast(temperatureBroadcastData, nil)
+ case readWriteModel.ApplicationId_ROOM_CONTROL_SYSTEM:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_LIGHTING:
+ // TODO: is this more are write?? maybe we a wrong here at the reader
+ var lightingData readWriteModel.LightingData
+ switch salCommand {
+ case readWriteModel.LightingCommandType_OFF.PLC4XEnumName():
+ commandTypeContainer := readWriteModel.LightingCommandTypeContainer_LightingCommandOff
+ if value == nil || !value.IsByte() {
+ return nil, false, false, false, errors.Errorf("%s requires exactly 1 arguments [group]", salCommand)
+ }
+ group := value.GetByte()
+ lightingData = readWriteModel.NewLightingDataOff(group, commandTypeContainer)
+ supportsWrite = true
+ case readWriteModel.LightingCommandType_ON.PLC4XEnumName():
+ commandTypeContainer := readWriteModel.LightingCommandTypeContainer_LightingCommandOn
+ if value == nil || (!value.IsByte() && (!value.IsList() || len(value.GetList()) != 1 || value.GetList()[0].IsByte())) {
+ return nil, false, false, false, errors.Errorf("%s requires exactly 1 arguments [group]", salCommand)
+ }
+ group := value.GetByte()
+ lightingData = readWriteModel.NewLightingDataOn(group, commandTypeContainer)
+ supportsWrite = true
+ case readWriteModel.LightingCommandType_RAMP_TO_LEVEL.PLC4XEnumName():
+ if value == nil || !value.IsList() || len(value.GetList()) != 3 || !value.GetList()[0].IsString() || !value.GetList()[1].IsByte() || !value.GetList()[2].IsByte() {
+ return nil, false, false, false, errors.Errorf("%s requires exactly 2 arguments [delay,group,level]", salCommand)
+ }
+ commandTypeContainer, ok := readWriteModel.LightingCommandTypeContainerByName(fmt.Sprintf("LightingCommandRampToLevel_%s", value.GetList()[0].GetString()))
+ if !ok {
+ var possibleValues []string
+ for _, v := range readWriteModel.LightingCommandTypeContainerValues {
+ possibleValues = append(possibleValues, strings.TrimPrefix(v.String(), "LightingCommandRampToLevel_"))
+ }
+ return nil, false, false, false, errors.Errorf("No level found for %s. Possible values %s", value.GetList()[0].GetString(), possibleValues)
+ }
+ group := value.GetList()[1].GetByte()
+ level := value.GetList()[2].GetByte()
+ lightingData = readWriteModel.NewLightingDataRampToLevel(group, level, commandTypeContainer)
+ supportsWrite = true
+ case readWriteModel.LightingCommandType_TERMINATE_RAMP.PLC4XEnumName():
+ commandTypeContainer := readWriteModel.LightingCommandTypeContainer_LightingCommandTerminateRamp
+ if value == nil || !value.IsByte() {
+ return nil, false, false, false, errors.Errorf("%s requires exactly 1 arguments [group]", salCommand)
+ }
+ group := value.GetByte()
+ lightingData = readWriteModel.NewLightingDataTerminateRamp(group, commandTypeContainer)
+ supportsWrite = true
+ case readWriteModel.LightingCommandType_LABEL.PLC4XEnumName():
+ panic("Implement me")
+ default:
+ return nil, false, false, false, errors.Errorf("Unsupported command %s for %s", salCommand, field.application.ApplicationId())
+ }
+ salData = readWriteModel.NewSALDataLighting(lightingData, nil)
+ case readWriteModel.ApplicationId_VENTILATION:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_IRRIGATION_CONTROL:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_POOLS_SPAS_PONDS_FOUNTAINS_CONTROL:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_HEATING:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_AIR_CONDITIONING:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_TRIGGER_CONTROL:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_ENABLE_CONTROL:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_AUDIO_AND_VIDEO:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_SECURITY:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_METERING:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_ACCESS_CONTROL:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_CLOCK_AND_TIMEKEEPING:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_TELEPHONY_STATUS_AND_CONTROL:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_MEASUREMENT:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_TESTING:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_MEDIA_TRANSPORT_CONTROL:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_ERROR_REPORTING:
+ panic("Implement me")
+ case readWriteModel.ApplicationId_HVAC_ACTUATOR:
+ panic("Implement me")
+ default:
+ return nil, false, false, false, errors.Errorf("No support for %s", field.application)
+ }
+ //TODO: we need support for bridged commands
+ command := readWriteModel.NewCBusPointToMultiPointCommandNormal(field.application, salData, 0x00, cbusOptions)
+ header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+ cbusCommand := readWriteModel.NewCBusCommandPointToMultiPoint(command, header, cbusOptions)
+ request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+ cBusMessage = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions)
+ return
+ default:
+ return nil, false, false, false, errors.Errorf("Unsupported type %T", field)
+ }
+}
diff --git a/plc4go/internal/cbus/FieldHandler.go b/plc4go/internal/cbus/FieldHandler.go
index 0efbda82e..adfdff9e0 100644
--- a/plc4go/internal/cbus/FieldHandler.go
+++ b/plc4go/internal/cbus/FieldHandler.go
@@ -105,236 +105,182 @@ var PossibleSalCommands = map[readWriteModel.ApplicationId][]string{
func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
if match := utils.GetSubgroupMatches(m.statusRequestPattern, query); match != nil {
- var startingGroupAddressLabel *byte
- var statusRequestType StatusRequestType
- statusRequestArgument := match["statusRequestType"]
- if statusRequestArgument != "" {
- if match["binary"] != "" {
- statusRequestType = StatusRequestTypeBinaryState
- } else if levelArgument := match["startingGroupAddressLabel"]; levelArgument != "" {
- statusRequestType = StatusRequestTypeLevel
- startingGroupAddressLabelArgument := match["startingGroupAddressLabel"]
- decodedHex, _ := hex.DecodeString(startingGroupAddressLabelArgument)
- if len(decodedHex) != 1 {
- panic("invalid state. Should have exactly 1")
- }
- startingGroupAddressLabel = &decodedHex[0]
- } else {
- return nil, errors.Errorf("Unknown statusRequestType%s", statusRequestArgument)
+ return m.handleStatusRequestPattern(match)
+ } else if match := utils.GetSubgroupMatches(m.calPattern, query); match != nil {
+ return m.handleCalPattern(match)
+ } else if match := utils.GetSubgroupMatches(m.salPattern, query); match != nil {
+ return m.handleSALPattern(match)
+ } else if match := utils.GetSubgroupMatches(m.salMonitorPattern, query); match != nil {
+ return m.handleSALMonitorPattern(match)
+ } else if match := utils.GetSubgroupMatches(m.mmiMonitorPattern, query); match != nil {
+ return m.handleMMIMonitorPattern(match)
+ } else if match := utils.GetSubgroupMatches(m.unityQuery, query); match != nil {
+ return m.handleUnitQuery(match)
+ } else {
+ return nil, errors.Errorf("Unable to parse %s", query)
+ }
+}
+
+func (m FieldHandler) handleStatusRequestPattern(match map[string]string) (model.PlcField, error) {
+ var startingGroupAddressLabel *byte
+ var statusRequestType StatusRequestType
+ statusRequestArgument := match["statusRequestType"]
+ if statusRequestArgument != "" {
+ if match["binary"] != "" {
+ statusRequestType = StatusRequestTypeBinaryState
+ } else if levelArgument := match["startingGroupAddressLabel"]; levelArgument != "" {
+ statusRequestType = StatusRequestTypeLevel
+ startingGroupAddressLabelArgument := match["startingGroupAddressLabel"]
+ decodedHex, _ := hex.DecodeString(startingGroupAddressLabelArgument)
+ if len(decodedHex) != 1 {
+ panic("invalid state. Should have exactly 1")
}
+ startingGroupAddressLabel = &decodedHex[0]
+ } else {
+ return nil, errors.Errorf("Unknown statusRequestType%s", statusRequestArgument)
}
- application, err := applicationIdFromArgument(match["application"])
+ }
+ application, err := applicationIdFromArgument(match["application"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error getting application id from argument")
+ }
+ return NewStatusField(statusRequestType, startingGroupAddressLabel, application, 1), nil
+}
+
+func (m FieldHandler) handleCalPattern(match map[string]string) (model.PlcField, error) {
+ var unitAddress readWriteModel.UnitAddress
+ unitAddressArgument := match["unitAddress"]
+ if strings.HasPrefix(unitAddressArgument, "0x") {
+ decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
if err != nil {
- return nil, errors.Wrap(err, "Error getting application id from argument")
+ return nil, errors.Wrap(err, "Not a valid hex")
}
- return NewStatusField(statusRequestType, startingGroupAddressLabel, application, 1), nil
- } else if match := utils.GetSubgroupMatches(m.calPattern, query); match != nil {
- var unitAddress readWriteModel.UnitAddress
- unitAddressArgument := match["unitAddress"]
- if strings.HasPrefix(unitAddressArgument, "0x") {
- decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
+ if len(decodedHex) != 1 {
+ return nil, errors.Errorf("Hex must be exatly one byte")
+ }
+ unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
+ } else {
+ atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
+ if err != nil {
+ return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
+ }
+ unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
+ }
+
+ calTypeArgument := match["calType"]
+ switch {
+ case strings.HasPrefix(calTypeArgument, "recall="):
+ var recalParamNo readWriteModel.Parameter
+ recallParamNoArgument := match["recallParamNo"]
+ if strings.HasPrefix(recallParamNoArgument, "0x") {
+ decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
if err != nil {
return nil, errors.Wrap(err, "Not a valid hex")
}
if len(decodedHex) != 1 {
return nil, errors.Errorf("Hex must be exatly one byte")
}
- unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
+ recalParamNo = readWriteModel.Parameter(decodedHex[0])
} else {
- atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
- if err != nil {
- return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
- }
- unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
- }
- calTypeArgument := match["calType"]
- switch {
- case strings.HasPrefix(calTypeArgument, "recall="):
- var recalParamNo readWriteModel.Parameter
- recallParamNoArgument := match["recallParamNo"]
- if strings.HasPrefix(recallParamNoArgument, "0x") {
- decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
- if err != nil {
- return nil, errors.Wrap(err, "Not a valid hex")
- }
- if len(decodedHex) != 1 {
- return nil, errors.Errorf("Hex must be exatly one byte")
- }
- recalParamNo = readWriteModel.Parameter(decodedHex[0])
+ if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
+ recalParamNo = readWriteModel.Parameter(atoi)
} else {
-
- if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
- recalParamNo = readWriteModel.Parameter(atoi)
- } else {
- parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
- if !ok {
- return nil, errors.Errorf("Unknown recallParamNo %s", recallParamNoArgument)
- }
- recalParamNo = parameterByName
+ parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
+ if !ok {
+ return nil, errors.Errorf("Unknown recallParamNo %s", recallParamNoArgument)
}
+ recalParamNo = parameterByName
}
- var count uint8
- atoi, err := strconv.ParseUint(match["recallCount"], 10, 8)
+ }
+ var count uint8
+ atoi, err := strconv.ParseUint(match["recallCount"], 10, 8)
+ if err != nil {
+ return nil, errors.Wrap(err, "recallCount not a valid number")
+ }
+ count = uint8(atoi)
+ return NewCALRecallField(unitAddress, recalParamNo, count, 1), nil
+ case strings.HasPrefix(calTypeArgument, "identify="):
+ var attribute readWriteModel.Attribute
+ attributeArgument := match["identifyAttribute"]
+ if strings.HasPrefix(attributeArgument, "0x") {
+ decodedHex, err := hex.DecodeString(attributeArgument[2:])
if err != nil {
- return nil, errors.Wrap(err, "recallCount not a valid number")
+ return nil, errors.Wrap(err, "Not a valid hex")
}
- count = uint8(atoi)
- return NewCALRecallField(unitAddress, recalParamNo, count, 1), nil
- case strings.HasPrefix(calTypeArgument, "identify="):
- var attribute readWriteModel.Attribute
- attributeArgument := match["identifyAttribute"]
- if strings.HasPrefix(attributeArgument, "0x") {
- decodedHex, err := hex.DecodeString(attributeArgument[2:])
- if err != nil {
- return nil, errors.Wrap(err, "Not a valid hex")
- }
- if len(decodedHex) != 1 {
- return nil, errors.Errorf("Hex must be exatly one byte")
- }
- attribute = readWriteModel.Attribute(decodedHex[0])
- } else {
- if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
- attribute = readWriteModel.Attribute(atoi)
- } else {
- parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
- if !ok {
- return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
- }
- attribute = parameterByName
- }
+ if len(decodedHex) != 1 {
+ return nil, errors.Errorf("Hex must be exatly one byte")
}
- return NewCALIdentifyField(unitAddress, attribute, 1), nil
- case strings.HasPrefix(calTypeArgument, "getstatus="):
- var recalParamNo readWriteModel.Parameter
- recallParamNoArgument := match["getstatusParamNo"]
- if strings.HasPrefix(recallParamNoArgument, "0x") {
- decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
- if err != nil {
- return nil, errors.Wrap(err, "Not a valid hex")
- }
- if len(decodedHex) != 1 {
- return nil, errors.Errorf("Hex must be exatly one byte")
- }
- recalParamNo = readWriteModel.Parameter(decodedHex[0])
+ attribute = readWriteModel.Attribute(decodedHex[0])
+ } else {
+ if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
+ attribute = readWriteModel.Attribute(atoi)
} else {
- if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
- recalParamNo = readWriteModel.Parameter(atoi)
- } else {
- parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
- if !ok {
- return nil, errors.Errorf("Unknown getstatusParamNo %s", recallParamNoArgument)
- }
- recalParamNo = parameterByName
+ parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
+ if !ok {
+ return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
}
+ attribute = parameterByName
}
- var count uint8
- atoi, err := strconv.ParseUint(match["getstatusCount"], 10, 8)
+ }
+ return NewCALIdentifyField(unitAddress, attribute, 1), nil
+ case strings.HasPrefix(calTypeArgument, "getstatus="):
+ var recalParamNo readWriteModel.Parameter
+ recallParamNoArgument := match["getstatusParamNo"]
+ if strings.HasPrefix(recallParamNoArgument, "0x") {
+ decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
if err != nil {
- return nil, errors.Wrap(err, "getstatusCount not a valid number")
+ return nil, errors.Wrap(err, "Not a valid hex")
}
- count = uint8(atoi)
- return NewCALGetstatusField(unitAddress, recalParamNo, count, 1), nil
- default:
- return nil, errors.Errorf("Invalid cal type %s", calTypeArgument)
- }
- } else if match := utils.GetSubgroupMatches(m.salPattern, query); match != nil {
- application, err := applicationIdFromArgument(match["application"])
- if err != nil {
- return nil, errors.Wrap(err, "Error getting application id from argument")
- }
- salCommand := match["salCommand"]
- if salCommand == "" {
- return nil, errors.Wrap(err, "Error getting salCommand from argument")
- }
- isValid := false
- for _, request := range PossibleSalCommands[application.ApplicationId()] {
- isValid = isValid || strings.HasPrefix(salCommand, request)
- }
- if !isValid {
- return nil, errors.Errorf("Invalid sal command %s for %s. Allowed requests: %s", salCommand, application, PossibleSalCommands[application.ApplicationId()])
- }
- panic("Implement me")
- } else if match := utils.GetSubgroupMatches(m.salMonitorPattern, query); match != nil {
- var unitAddress readWriteModel.UnitAddress
- {
- unitAddressArgument := match["unitAddress"]
- if unitAddressArgument == "*" {
- unitAddress = nil
- } else if strings.HasPrefix(unitAddressArgument, "0x") {
- decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
- if err != nil {
- return nil, errors.Wrap(err, "Not a valid hex")
- }
- if len(decodedHex) != 1 {
- return nil, errors.Errorf("Hex must be exatly one byte")
- }
- unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
- } else {
- atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
- if err != nil {
- return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
- }
- unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
+ if len(decodedHex) != 1 {
+ return nil, errors.Errorf("Hex must be exatly one byte")
}
- }
-
- var application readWriteModel.ApplicationIdContainer
- {
- applicationIdArgument := match["application"]
- if applicationIdArgument == "*" {
- application = readWriteModel.ApplicationIdContainer_RESERVED_FF
+ recalParamNo = readWriteModel.Parameter(decodedHex[0])
+ } else {
+ if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
+ recalParamNo = readWriteModel.Parameter(atoi)
} else {
- var err error
- application, err = applicationIdFromArgument(applicationIdArgument)
- if err != nil {
- return nil, errors.Wrap(err, "Error getting application id from argument")
+ parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
+ if !ok {
+ return nil, errors.Errorf("Unknown getstatusParamNo %s", recallParamNoArgument)
}
+ recalParamNo = parameterByName
}
}
-
- return NewSALMonitorField(unitAddress, application, 1), nil
- } else if match := utils.GetSubgroupMatches(m.mmiMonitorPattern, query); match != nil {
- var unitAddress readWriteModel.UnitAddress
- {
- unitAddressArgument := match["unitAddress"]
- if unitAddressArgument == "*" {
- unitAddress = nil
- } else if strings.HasPrefix(unitAddressArgument, "0x") {
- decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
- if err != nil {
- return nil, errors.Wrap(err, "Not a valid hex")
- }
- if len(decodedHex) != 1 {
- return nil, errors.Errorf("Hex must be exatly one byte")
- }
- unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
- } else {
- atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
- if err != nil {
- return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
- }
- unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
- }
+ var count uint8
+ atoi, err := strconv.ParseUint(match["getstatusCount"], 10, 8)
+ if err != nil {
+ return nil, errors.Wrap(err, "getstatusCount not a valid number")
}
+ count = uint8(atoi)
+ return NewCALGetstatusField(unitAddress, recalParamNo, count, 1), nil
+ default:
+ return nil, errors.Errorf("Invalid cal type %s", calTypeArgument)
+ }
+}
- var application readWriteModel.ApplicationIdContainer
- {
- applicationIdArgument := match["application"]
- if applicationIdArgument == "*" {
- application = readWriteModel.ApplicationIdContainer_RESERVED_FF
- } else {
- var err error
- application, err = applicationIdFromArgument(applicationIdArgument)
- if err != nil {
- return nil, errors.Wrap(err, "Error getting application id from argument")
- }
- }
- }
+func (m FieldHandler) handleSALPattern(match map[string]string) (model.PlcField, error) {
+ application, err := applicationIdFromArgument(match["application"])
+ if err != nil {
+ return nil, errors.Wrap(err, "Error getting application id from argument")
+ }
+ salCommand := match["salCommand"]
+ if salCommand == "" {
+ return nil, errors.Wrap(err, "Error getting salCommand from argument")
+ }
+ isValid := false
+ for _, request := range PossibleSalCommands[application.ApplicationId()] {
+ isValid = isValid || strings.HasPrefix(salCommand, request)
+ }
+ if !isValid {
+ return nil, errors.Errorf("Invalid sal command %s for %s. Allowed requests: %s", salCommand, application, PossibleSalCommands[application.ApplicationId()])
+ }
+ return NewSALField(application, salCommand, 1), nil
+}
- return NewMMIMonitorField(unitAddress, application, 1), nil
- } else if match := utils.GetSubgroupMatches(m.unityQuery, query); match != nil {
- var unitAddress *readWriteModel.UnitAddress
+func (m FieldHandler) handleSALMonitorPattern(match map[string]string) (model.PlcField, error) {
+ var unitAddress readWriteModel.UnitAddress
+ {
unitAddressArgument := match["unitAddress"]
if unitAddressArgument == "*" {
unitAddress = nil
@@ -346,53 +292,131 @@ func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
if len(decodedHex) != 1 {
return nil, errors.Errorf("Hex must be exatly one byte")
}
- var unitAddressVar readWriteModel.UnitAddress
- unitAddressVar = readWriteModel.NewUnitAddress(decodedHex[0])
- unitAddress = &unitAddressVar
+ unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
} else {
atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
if err != nil {
return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
}
- var unitAddressVar readWriteModel.UnitAddress
- unitAddressVar = readWriteModel.NewUnitAddress(byte(atoi))
- unitAddress = &unitAddressVar
+ unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
}
+ }
- var attribute *readWriteModel.Attribute
- attributeArgument := match["identifyAttribute"]
- if attributeArgument == "*" {
- attribute = nil
- } else if strings.HasPrefix(attributeArgument, "0x") {
- decodedHex, err := hex.DecodeString(attributeArgument[2:])
+ var application readWriteModel.ApplicationIdContainer
+ {
+ applicationIdArgument := match["application"]
+ if applicationIdArgument == "*" {
+ application = readWriteModel.ApplicationIdContainer_RESERVED_FF
+ } else {
+ var err error
+ application, err = applicationIdFromArgument(applicationIdArgument)
+ if err != nil {
+ return nil, errors.Wrap(err, "Error getting application id from argument")
+ }
+ }
+ }
+
+ return NewSALMonitorField(unitAddress, application, 1), nil
+}
+
+func (m FieldHandler) handleMMIMonitorPattern(match map[string]string) (model.PlcField, error) {
+ var unitAddress readWriteModel.UnitAddress
+ {
+ unitAddressArgument := match["unitAddress"]
+ if unitAddressArgument == "*" {
+ unitAddress = nil
+ } else if strings.HasPrefix(unitAddressArgument, "0x") {
+ decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
if err != nil {
return nil, errors.Wrap(err, "Not a valid hex")
}
if len(decodedHex) != 1 {
return nil, errors.Errorf("Hex must be exatly one byte")
}
+ unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
+ } else {
+ atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
+ if err != nil {
+ return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
+ }
+ unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
+ }
+ }
+
+ var application readWriteModel.ApplicationIdContainer
+ {
+ applicationIdArgument := match["application"]
+ if applicationIdArgument == "*" {
+ application = readWriteModel.ApplicationIdContainer_RESERVED_FF
+ } else {
+ var err error
+ application, err = applicationIdFromArgument(applicationIdArgument)
+ if err != nil {
+ return nil, errors.Wrap(err, "Error getting application id from argument")
+ }
+ }
+ }
+
+ return NewMMIMonitorField(unitAddress, application, 1), nil
+}
+
+func (m FieldHandler) handleUnitQuery(match map[string]string) (model.PlcField, error) {
+ var unitAddress *readWriteModel.UnitAddress
+ unitAddressArgument := match["unitAddress"]
+ if unitAddressArgument == "*" {
+ unitAddress = nil
+ } else if strings.HasPrefix(unitAddressArgument, "0x") {
+ decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
+ if err != nil {
+ return nil, errors.Wrap(err, "Not a valid hex")
+ }
+ if len(decodedHex) != 1 {
+ return nil, errors.Errorf("Hex must be exatly one byte")
+ }
+ var unitAddressVar readWriteModel.UnitAddress
+ unitAddressVar = readWriteModel.NewUnitAddress(decodedHex[0])
+ unitAddress = &unitAddressVar
+ } else {
+ atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
+ if err != nil {
+ return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
+ }
+ var unitAddressVar readWriteModel.UnitAddress
+ unitAddressVar = readWriteModel.NewUnitAddress(byte(atoi))
+ unitAddress = &unitAddressVar
+ }
+
+ var attribute *readWriteModel.Attribute
+ attributeArgument := match["identifyAttribute"]
+ if attributeArgument == "*" {
+ attribute = nil
+ } else if strings.HasPrefix(attributeArgument, "0x") {
+ decodedHex, err := hex.DecodeString(attributeArgument[2:])
+ if err != nil {
+ return nil, errors.Wrap(err, "Not a valid hex")
+ }
+ if len(decodedHex) != 1 {
+ return nil, errors.Errorf("Hex must be exatly one byte")
+ }
+ var attributeVar readWriteModel.Attribute
+ attributeVar = readWriteModel.Attribute(decodedHex[0])
+ attribute = &attributeVar
+ } else {
+ if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
var attributeVar readWriteModel.Attribute
- attributeVar = readWriteModel.Attribute(decodedHex[0])
+ attributeVar = readWriteModel.Attribute(atoi)
attribute = &attributeVar
} else {
- if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
- var attributeVar readWriteModel.Attribute
- attributeVar = readWriteModel.Attribute(atoi)
- attribute = &attributeVar
- } else {
- parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
- if !ok {
- return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
- }
- var attributeVar readWriteModel.Attribute
- attributeVar = parameterByName
- attribute = &attributeVar
+ parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
+ if !ok {
+ return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
}
+ var attributeVar readWriteModel.Attribute
+ attributeVar = parameterByName
+ attribute = &attributeVar
}
- return NewUnitInfoField(unitAddress, attribute, 1), nil
- } else {
- return nil, errors.Errorf("Unable to parse %s", query)
}
+ return NewUnitInfoField(unitAddress, attribute, 1), nil
}
func applicationIdFromArgument(applicationIdArgument string) (readWriteModel.ApplicationIdContainer, error) {
diff --git a/plc4go/internal/cbus/Reader.go b/plc4go/internal/cbus/Reader.go
index bbc90e79c..e0712f4a2 100644
--- a/plc4go/internal/cbus/Reader.go
+++ b/plc4go/internal/cbus/Reader.go
@@ -26,8 +26,8 @@ import (
"sync"
"time"
- "github.com/apache/plc4x/plc4go/pkg/api/model"
- "github.com/apache/plc4x/plc4go/pkg/api/values"
+ apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
+ apiValues "github.com/apache/plc4x/plc4go/pkg/api/values"
readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
"github.com/apache/plc4x/plc4go/spi"
spiModel "github.com/apache/plc4x/plc4go/spi/model"
@@ -51,13 +51,12 @@ func NewReader(tpduGenerator *AlphaGenerator, messageCodec spi.MessageCodec, tm
}
}
-func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-chan model.PlcReadRequestResult {
- // TODO: handle ctx
+func (m *Reader) Read(ctx context.Context, readRequest apiModel.PlcReadRequest) <-chan apiModel.PlcReadRequestResult {
log.Trace().Msg("Reading")
- result := make(chan model.PlcReadRequestResult)
+ result := make(chan apiModel.PlcReadRequestResult)
go func() {
numFields := len(readRequest.GetFieldNames())
- if numFields > 20 {
+ if numFields > 20 { // letters g-z
result <- &spiModel.DefaultPlcReadRequestResult{
Request: readRequest,
Response: nil,
@@ -68,7 +67,15 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
messages := make(map[string]readWriteModel.CBusMessage)
for _, fieldName := range readRequest.GetFieldNames() {
field := readRequest.GetField(fieldName)
- message, err := m.fieldToCBusMessage(field)
+ message, supportsRead, _, _, err := FieldToCBusMessage(field, nil, m.alphaGenerator, m.messageCodec.(*MessageCodec))
+ if !supportsRead {
+ result <- &spiModel.DefaultPlcReadRequestResult{
+ Request: readRequest,
+ Response: nil,
+ Err: errors.Wrapf(err, "Error encoding cbus message for field %s. Field is not meant to be read.", fieldName),
+ }
+ return
+ }
if err != nil {
result <- &spiModel.DefaultPlcReadRequestResult{
Request: readRequest,
@@ -80,26 +87,31 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
messages[fieldName] = message
}
responseMu := sync.Mutex{}
- responseCodes := map[string]model.PlcResponseCode{}
- addResponseCode := func(name string, responseCode model.PlcResponseCode) {
+ responseCodes := map[string]apiModel.PlcResponseCode{}
+ addResponseCode := func(name string, responseCode apiModel.PlcResponseCode) {
responseMu.Lock()
defer responseMu.Unlock()
responseCodes[name] = responseCode
}
valueMu := sync.Mutex{}
- plcValues := map[string]values.PlcValue{}
- addPlcValue := func(name string, plcValue values.PlcValue) {
+ plcValues := map[string]apiValues.PlcValue{}
+ addPlcValue := func(name string, plcValue apiValues.PlcValue) {
valueMu.Lock()
defer valueMu.Unlock()
plcValues[name] = plcValue
}
for fieldName, messageToSend := range messages {
+ if err := ctx.Err(); err != nil {
+ result <- &spiModel.DefaultPlcReadRequestResult{
+ Request: readRequest,
+ Err: err,
+ }
+ return
+ }
fieldNameCopy := fieldName
// Start a new request-transaction (Is ended in the response-handler)
- requestWasOk := make(chan bool)
transaction := m.tm.StartTransaction()
transaction.Submit(func() {
-
// Send the over the wire
log.Trace().Msg("Send ")
if err := m.messageCodec.SendRequest(ctx, messageToSend, func(receivedMessage spi.Message) bool {
@@ -129,28 +141,26 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
messageToClient := cbusMessage.(readWriteModel.CBusMessageToClient)
if _, ok := messageToClient.GetReply().(readWriteModel.ServerErrorReplyExactly); ok {
log.Trace().Msg("We got a server failure")
- addResponseCode(fieldNameCopy, model.PlcResponseCode_INVALID_DATA)
- requestWasOk <- false
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INVALID_DATA)
return transaction.EndRequest()
}
replyOrConfirmationConfirmation := messageToClient.GetReply().(readWriteModel.ReplyOrConfirmationConfirmationExactly)
if !replyOrConfirmationConfirmation.GetConfirmation().GetIsSuccess() {
- var responseCode model.PlcResponseCode
+ var responseCode apiModel.PlcResponseCode
switch replyOrConfirmationConfirmation.GetConfirmation().GetConfirmationType() {
case readWriteModel.ConfirmationType_NOT_TRANSMITTED_TO_MANY_RE_TRANSMISSIONS:
- responseCode = model.PlcResponseCode_REMOTE_ERROR
+ responseCode = apiModel.PlcResponseCode_REMOTE_ERROR
case readWriteModel.ConfirmationType_NOT_TRANSMITTED_CORRUPTION:
- responseCode = model.PlcResponseCode_INVALID_DATA
+ responseCode = apiModel.PlcResponseCode_INVALID_DATA
case readWriteModel.ConfirmationType_NOT_TRANSMITTED_SYNC_LOSS:
- responseCode = model.PlcResponseCode_REMOTE_BUSY
+ responseCode = apiModel.PlcResponseCode_REMOTE_BUSY
case readWriteModel.ConfirmationType_NOT_TRANSMITTED_TOO_LONG:
- responseCode = model.PlcResponseCode_INVALID_DATA
+ responseCode = apiModel.PlcResponseCode_INVALID_DATA
default:
panic("Every code should be mapped here")
}
log.Trace().Msgf("Was no success %s:%v", fieldNameCopy, responseCode)
addResponseCode(fieldNameCopy, responseCode)
- requestWasOk <- true
return transaction.EndRequest()
}
@@ -159,8 +169,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
embeddedReply, ok := replyOrConfirmationConfirmation.GetEmbeddedReply().(readWriteModel.ReplyOrConfirmationReplyExactly)
if !ok {
log.Trace().Msgf("Is a confirm only, no data. Alpha: %c", alpha.GetCharacter())
- addResponseCode(fieldNameCopy, model.PlcResponseCode_NOT_FOUND)
- requestWasOk <- true
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_NOT_FOUND)
return transaction.EndRequest()
}
@@ -169,7 +178,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
switch reply := embeddedReply.GetReply().(readWriteModel.ReplyEncodedReply).GetEncodedReply().(type) {
case readWriteModel.EncodedReplyCALReplyExactly:
calData := reply.GetCalReply().GetCalData()
- addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
switch calData := calData.(type) {
case readWriteModel.CALDataStatusExactly:
application := calData.GetApplication()
@@ -179,8 +188,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
// TODO: verify application... this should be the same
_ = blockStart
statusBytes := calData.GetStatusBytes()
- addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
- plcListValues := make([]values.PlcValue, len(statusBytes)*4)
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
+ plcListValues := make([]apiValues.PlcValue, len(statusBytes)*4)
for i, statusByte := range statusBytes {
plcListValues[i*4+0] = spiValues.NewPlcSTRING(statusByte.GetGav0().String())
plcListValues[i*4+1] = spiValues.NewPlcSTRING(statusByte.GetGav1().String())
@@ -203,8 +212,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
fallthrough
case readWriteModel.StatusCoding_BINARY_BY_ELSEWHERE:
statusBytes := calData.GetStatusBytes()
- addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
- plcListValues := make([]values.PlcValue, len(statusBytes)*4)
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
+ plcListValues := make([]apiValues.PlcValue, len(statusBytes)*4)
for i, statusByte := range statusBytes {
plcListValues[i*4+0] = spiValues.NewPlcSTRING(statusByte.GetGav0().String())
plcListValues[i*4+1] = spiValues.NewPlcSTRING(statusByte.GetGav1().String())
@@ -216,8 +225,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
fallthrough
case readWriteModel.StatusCoding_LEVEL_BY_ELSEWHERE:
levelInformation := calData.GetLevelInformation()
- addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
- plcListValues := make([]values.PlcValue, len(levelInformation))
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
+ plcListValues := make([]apiValues.PlcValue, len(levelInformation))
for i, levelInformation := range levelInformation {
switch levelInformation := levelInformation.(type) {
case readWriteModel.LevelInformationAbsentExactly:
@@ -237,12 +246,12 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
case readWriteModel.IdentifyReplyCommandCurrentSenseLevelsExactly:
addPlcValue(fieldNameCopy, spiValues.NewPlcByteArray(identifyReplyCommand.GetCurrentSenseLevels()))
case readWriteModel.IdentifyReplyCommandDelaysExactly:
- addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+ addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
"ReStrikeDelay": spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetReStrikeDelay())),
"TerminalLevel": spiValues.NewPlcByteArray(identifyReplyCommand.GetTerminalLevels()),
}))
case readWriteModel.IdentifyReplyCommandDSIStatusExactly:
- addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+ addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
"ChannelStatus1": spiValues.NewPlcSTRING(identifyReplyCommand.GetChannelStatus1().String()),
"ChannelStatus2": spiValues.NewPlcSTRING(identifyReplyCommand.GetChannelStatus2().String()),
"ChannelStatus3": spiValues.NewPlcSTRING(identifyReplyCommand.GetChannelStatus3().String()),
@@ -255,7 +264,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
"DimmingUCRevisionNumber": spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetDimmingUCRevisionNumber())),
}))
case readWriteModel.IdentifyReplyCommandExtendedDiagnosticSummaryExactly:
- addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+ addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
"LowApplication": spiValues.NewPlcSTRING(identifyReplyCommand.GetLowApplication().String()),
"HighApplication": spiValues.NewPlcSTRING(identifyReplyCommand.GetHighApplication().String()),
"Area": spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetArea())),
@@ -277,7 +286,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
"MicroPowerReset": spiValues.NewPlcBOOL(identifyReplyCommand.GetMicroPowerReset()),
}))
case readWriteModel.IdentifyReplyCommandSummaryExactly:
- addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+ addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
"PartName": spiValues.NewPlcSTRING(identifyReplyCommand.GetPartName()),
"UnitServiceType": spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetUnitServiceType())),
"Version": spiValues.NewPlcSTRING(identifyReplyCommand.GetVersion()),
@@ -291,9 +300,9 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
case readWriteModel.IdentifyReplyCommandGAVValuesStoredExactly:
addPlcValue(fieldNameCopy, spiValues.NewPlcByteArray(identifyReplyCommand.GetValues()))
case readWriteModel.IdentifyReplyCommandLogicalAssignmentExactly:
- var plcValues []values.PlcValue
+ var plcValues []apiValues.PlcValue
for _, logicAssigment := range identifyReplyCommand.GetLogicAssigment() {
- plcValues = append(plcValues, spiValues.NewPlcStruct(map[string]values.PlcValue{
+ plcValues = append(plcValues, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
"GreaterOfOrLogic": spiValues.NewPlcBOOL(logicAssigment.GetGreaterOfOrLogic()),
"ReStrikeDelay": spiValues.NewPlcBOOL(logicAssigment.GetReStrikeDelay()),
"AssignedToGav16": spiValues.NewPlcBOOL(logicAssigment.GetAssignedToGav16()),
@@ -326,8 +335,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
addPlcValue(fieldNameCopy, spiValues.NewPlcLREAL(voltsFloat))
case readWriteModel.IdentifyReplyCommandOutputUnitSummaryExactly:
unitFlags := identifyReplyCommand.GetUnitFlags()
- structContent := map[string]values.PlcValue{
- "UnitFlags": spiValues.NewPlcStruct(map[string]values.PlcValue{
+ structContent := map[string]apiValues.PlcValue{
+ "UnitFlags": spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
"AssertingNetworkBurden": spiValues.NewPlcBOOL(unitFlags.GetAssertingNetworkBurden()),
"RestrikeTimingActive": spiValues.NewPlcBOOL(unitFlags.GetRestrikeTimingActive()),
"RemoteOFFInputAsserted": spiValues.NewPlcBOOL(unitFlags.GetRemoteOFFInputAsserted()),
@@ -352,7 +361,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
addPlcValue(fieldNameCopy, spiValues.NewPlcSTRING(identifyReplyCommand.GetUnitType()))
default:
log.Error().Msgf("Unmapped type %T", identifyReplyCommand)
- requestWasOk <- false
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INVALID_DATA)
return transaction.EndRequest()
}
default:
@@ -362,25 +371,18 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
default:
panic(fmt.Sprintf("All types should be mapped here. Not mapped: %T", reply))
}
- requestWasOk <- true
return transaction.EndRequest()
}, func(err error) error {
log.Debug().Msgf("Error waiting for field %s", fieldNameCopy)
- addResponseCode(fieldNameCopy, model.PlcResponseCode_REQUEST_TIMEOUT)
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_REQUEST_TIMEOUT)
// TODO: ok or not ok?
- requestWasOk <- true
return transaction.EndRequest()
}, time.Second*1); err != nil {
log.Debug().Err(err).Msgf("Error sending message for field %s", fieldNameCopy)
- addResponseCode(fieldNameCopy, model.PlcResponseCode_INTERNAL_ERROR)
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INTERNAL_ERROR)
_ = transaction.EndRequest()
- requestWasOk <- false
}
})
- if !<-requestWasOk {
- // TODO: if we found a error we can abort
- break
- }
}
readResponse := spiModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues)
result <- &spiModel.DefaultPlcReadRequestResult{
@@ -390,49 +392,3 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
}()
return result
}
-
-func (m *Reader) fieldToCBusMessage(field model.PlcField) (readWriteModel.CBusMessage, error) {
- cbusOptions := m.messageCodec.(*MessageCodec).cbusOptions
- requestContext := m.messageCodec.(*MessageCodec).requestContext
- switch field := field.(type) {
- case *statusField:
- var statusRequest readWriteModel.StatusRequest
- switch field.statusRequestType {
- case StatusRequestTypeBinaryState:
- statusRequest = readWriteModel.NewStatusRequestBinaryState(field.application, 0x7A)
- case StatusRequestTypeLevel:
- statusRequest = readWriteModel.NewStatusRequestLevel(field.application, *field.startingGroupAddressLabel, 0x73)
- }
- command := readWriteModel.NewCBusPointToMultiPointCommandStatus(statusRequest, byte(field.application), cbusOptions)
- header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToMultiPoint)
- cbusCommand := readWriteModel.NewCBusCommandPointToMultiPoint(command, header, cbusOptions)
- request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
- return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
- case *calRecallField:
- calData := readWriteModel.NewCALDataRecall(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandRecall, nil, requestContext)
- //TODO: we need support for bridged commands
- command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
- header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
- cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
- request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
- return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
- case *calIdentifyField:
- calData := readWriteModel.NewCALDataIdentify(field.attribute, readWriteModel.CALCommandTypeContainer_CALCommandIdentify, nil, requestContext)
- //TODO: we need support for bridged commands
- command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
- header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
- cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
- request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
- return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
- case *calGetstatusField:
- calData := readWriteModel.NewCALDataGetStatus(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandGetStatus, nil, requestContext)
- //TODO: we need support for bridged commands
- command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
- header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
- cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
- request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
- return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
- default:
- return nil, errors.Errorf("Unmapped type %T", field)
- }
-}
diff --git a/plc4go/internal/cbus/Writer.go b/plc4go/internal/cbus/Writer.go
index b901f8493..077291b6a 100644
--- a/plc4go/internal/cbus/Writer.go
+++ b/plc4go/internal/cbus/Writer.go
@@ -21,10 +21,14 @@ package cbus
import (
"context"
- "github.com/apache/plc4x/plc4go/pkg/api/model"
+ apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
+ readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
"github.com/apache/plc4x/plc4go/spi"
- plc4goModel "github.com/apache/plc4x/plc4go/spi/model"
+ spiModel "github.com/apache/plc4x/plc4go/spi/model"
"github.com/pkg/errors"
+ "github.com/rs/zerolog/log"
+ "sync"
+ "time"
)
type Writer struct {
@@ -41,14 +45,104 @@ func NewWriter(tpduGenerator *AlphaGenerator, messageCodec spi.MessageCodec, tm
}
}
-func (m Writer) Write(ctx context.Context, writeRequest model.PlcWriteRequest) <-chan model.PlcWriteRequestResult {
- // TODO: handle context
- result := make(chan model.PlcWriteRequestResult)
+func (m Writer) Write(ctx context.Context, writeRequest apiModel.PlcWriteRequest) <-chan apiModel.PlcWriteRequestResult {
+ log.Trace().Msg("Writing")
+ result := make(chan apiModel.PlcWriteRequestResult)
go func() {
- result <- &plc4goModel.DefaultPlcWriteRequestResult{
+ numFields := len(writeRequest.GetFieldNames())
+ if numFields > 20 { // letters g-z
+ result <- &spiModel.DefaultPlcWriteRequestResult{
+ Request: writeRequest,
+ Response: nil,
+ Err: errors.New("Only 20 fields can be handled at once"),
+ }
+ return
+ }
+
+ messages := make(map[string]readWriteModel.CBusMessage)
+ for _, fieldName := range writeRequest.GetFieldNames() {
+ field := writeRequest.GetField(fieldName)
+ plcValue := writeRequest.GetValue(fieldName)
+ message, _, supportsWrite, _, err := FieldToCBusMessage(field, plcValue, m.alphaGenerator, m.messageCodec.(*MessageCodec))
+ if !supportsWrite {
+ result <- &spiModel.DefaultPlcWriteRequestResult{
+ Request: writeRequest,
+ Response: nil,
+ Err: errors.Wrapf(err, "Error encoding cbus message for field %s. Field is not meant to be written.", fieldName),
+ }
+ return
+ }
+ if err != nil {
+ result <- &spiModel.DefaultPlcWriteRequestResult{
+ Request: writeRequest,
+ Response: nil,
+ Err: errors.Wrapf(err, "Error encoding cbus message for field %s", fieldName),
+ }
+ return
+ }
+ messages[fieldName] = message
+ }
+ responseMu := sync.Mutex{}
+ responseCodes := map[string]apiModel.PlcResponseCode{}
+ addResponseCode := func(name string, responseCode apiModel.PlcResponseCode) {
+ responseMu.Lock()
+ defer responseMu.Unlock()
+ responseCodes[name] = responseCode
+ }
+ for fieldName, messageToSend := range messages {
+ if err := ctx.Err(); err != nil {
+ result <- &spiModel.DefaultPlcWriteRequestResult{
+ Request: writeRequest,
+ Err: err,
+ }
+ return
+ }
+ fieldNameCopy := fieldName
+ // Start a new request-transaction (Is ended in the response-handler)
+ transaction := m.tm.StartTransaction()
+ transaction.Submit(func() {
+ // Send the over the wire
+ log.Trace().Msg("Send ")
+ if err := m.messageCodec.SendRequest(ctx, messageToSend, func(receivedMessage spi.Message) bool {
+ cbusMessage, ok := receivedMessage.(readWriteModel.CBusMessageExactly)
+ if !ok {
+ return false
+ }
+ messageToClient, ok := cbusMessage.(readWriteModel.CBusMessageToClientExactly)
+ if !ok {
+ return false
+ }
+ // Check if this errored
+ if _, ok = messageToClient.GetReply().(readWriteModel.ServerErrorReplyExactly); ok {
+ // This means we must handle this below
+ return true
+ }
+
+ confirmation, ok := messageToClient.GetReply().(readWriteModel.ReplyOrConfirmationConfirmationExactly)
+ if !ok {
+ return false
+ }
+ return confirmation.GetConfirmation().GetAlpha().GetCharacter() == messageToSend.(readWriteModel.CBusMessageToServer).GetRequest().(readWriteModel.RequestCommand).GetAlpha().GetCharacter()
+ }, func(receivedMessage spi.Message) error {
+ // Convert the response into an
+ addResponseCode(fieldName, apiModel.PlcResponseCode_OK)
+ return transaction.EndRequest()
+ }, func(err error) error {
+ log.Debug().Msgf("Error waiting for field %s", fieldNameCopy)
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_REQUEST_TIMEOUT)
+ // TODO: ok or not ok?
+ return transaction.EndRequest()
+ }, time.Second*1); err != nil {
+ log.Debug().Err(err).Msgf("Error sending message for field %s", fieldNameCopy)
+ addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INTERNAL_ERROR)
+ _ = transaction.EndRequest()
+ }
+ })
+ }
+ readResponse := spiModel.NewDefaultPlcWriteResponse(writeRequest, responseCodes)
+ result <- &spiModel.DefaultPlcWriteRequestResult{
Request: writeRequest,
- Response: nil,
- Err: errors.New("Not yet implemented"),
+ Response: readResponse,
}
}()
return result