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/17 14:12:48 UTC
[plc4x] 03/03: feat(plc4go/cbus): introduced sal field
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 2651c29520b20f9047dcae873614ec504f069031
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Wed Aug 17 16:12:38 2022 +0200
feat(plc4go/cbus): introduced sal field
---
plc4go/internal/cbus/Field.go | 69 +++++++++++++++++++
plc4go/internal/cbus/FieldHandler.go | 55 +++++++++++++++
plc4go/internal/cbus/Subscriber.go | 113 ++++++++++++++++++++++++-------
plc4go/internal/cbus/fieldtype_string.go | 11 +--
4 files changed, 218 insertions(+), 30 deletions(-)
diff --git a/plc4go/internal/cbus/Field.go b/plc4go/internal/cbus/Field.go
index 76a89ff70..1a56bf62f 100644
--- a/plc4go/internal/cbus/Field.go
+++ b/plc4go/internal/cbus/Field.go
@@ -117,6 +117,22 @@ func NewCALGetstatusField(unitAddress readWriteModel.UnitAddress, parameter read
}
}
+// SALField can be used to send SAL commands
+type SALField interface {
+ model.PlcField
+ GetApplication() readWriteModel.ApplicationIdContainer
+ GetSALCommand() string
+}
+
+func NewSALField(application readWriteModel.ApplicationIdContainer, salCommand string, numElements uint16) SALField {
+ return &salField{
+ fieldType: SAL,
+ application: application,
+ salCommand: salCommand,
+ numElements: numElements,
+ }
+}
+
// SALMonitorField can be used to monitor sal fields
type SALMonitorField interface {
model.PlcField
@@ -207,6 +223,12 @@ type calGetstatusField struct {
numElements uint16
}
+type salField struct {
+ fieldType FieldType
+ application readWriteModel.ApplicationIdContainer
+ salCommand string
+ numElements uint16
+}
type salMonitorField struct {
fieldType FieldType
unitAddress readWriteModel.UnitAddress
@@ -445,6 +467,53 @@ func (c calGetstatusField) String() string {
return writeBuffer.GetBox().String()
}
+func (s salField) GetApplication() readWriteModel.ApplicationIdContainer {
+ return s.application
+}
+
+func (s salField) GetSALCommand() string {
+ return s.salCommand
+}
+
+func (s salField) GetAddressString() string {
+ return fmt.Sprintf("sal/%s/%s", s.application, s.salCommand)
+}
+
+func (s salField) GetTypeName() string {
+ return s.fieldType.GetName()
+}
+
+func (s salField) GetQuantity() uint16 {
+ return s.numElements
+}
+
+func (s salField) Serialize(writeBuffer utils.WriteBuffer) error {
+ if err := writeBuffer.PushContext(s.fieldType.GetName()); err != nil {
+ return err
+ }
+
+ if err := s.application.Serialize(writeBuffer); err != nil {
+ return err
+ }
+
+ if err := writeBuffer.WriteString("salCommand", uint32(len(s.salCommand)*8), "UTF-8", s.salCommand); err != nil {
+ return err
+ }
+
+ if err := writeBuffer.PopContext(s.fieldType.GetName()); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s salField) String() string {
+ writeBuffer := utils.NewBoxedWriteBufferWithOptions(true, true)
+ if err := writeBuffer.WriteSerializable(s); err != nil {
+ return err.Error()
+ }
+ return writeBuffer.GetBox().String()
+}
+
func (s salMonitorField) GetAddressString() string {
// TODO: this is nonsense... fix that
return fmt.Sprintf("%d/%s%s[%d]", s.fieldType, s.unitAddress, s.application, s.numElements)
diff --git a/plc4go/internal/cbus/FieldHandler.go b/plc4go/internal/cbus/FieldHandler.go
index 11ecb391e..0efbda82e 100644
--- a/plc4go/internal/cbus/FieldHandler.go
+++ b/plc4go/internal/cbus/FieldHandler.go
@@ -21,6 +21,7 @@ package cbus
import (
"encoding/hex"
+ "fmt"
"github.com/apache/plc4x/plc4go/pkg/api/model"
readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
"github.com/apache/plc4x/plc4go/spi/utils"
@@ -38,6 +39,7 @@ const (
CAL_RECALL
CAL_IDENTIFY
CAL_GETSTATUS
+ SAL
SAL_MONITOR
MMI_STATUS_MONITOR
UNIT_INFO
@@ -50,6 +52,7 @@ func (i FieldType) GetName() string {
type FieldHandler struct {
statusRequestPattern *regexp.Regexp
calPattern *regexp.Regexp
+ salPattern *regexp.Regexp
salMonitorPattern *regexp.Regexp
mmiMonitorPattern *regexp.Regexp
unityQuery *regexp.Regexp
@@ -59,12 +62,47 @@ func NewFieldHandler() FieldHandler {
return FieldHandler{
statusRequestPattern: regexp.MustCompile(`^status/(?P<statusRequestType>(?P<binary>binary)|level=0x(?P<startingGroupAddressLabel>00|20|40|60|80|A0|C0|E0))/(?P<application>.*)`),
calPattern: regexp.MustCompile(`^cal/(?P<unitAddress>.+)/(?P<calType>recall=\[(?P<recallParamNo>\w+), ?(?P<recallCount>\d+)]|identify=(?P<identifyAttribute>\w+)|getstatus=(?P<getstatusParamNo>\w+), ?(?P<getstatusCount>\d+))`),
+ salPattern: regexp.MustCompile(`^sal/(?P<application>.*)/(?P<salCommand>.*)`),
salMonitorPattern: regexp.MustCompile(`^salmonitor/(?P<unitAddress>.+)/(?P<application>.+)`),
mmiMonitorPattern: regexp.MustCompile(`^mmimonitor/(?P<unitAddress>.+)/(?P<application>.+)`),
unityQuery: regexp.MustCompile(`^info/(?P<unitAddress>.+)/(?P<identifyAttribute>.+)`),
}
}
+func ms2s[T fmt.Stringer](t []T) []string {
+ result := make([]string, len(t))
+ for i, stringer := range t {
+ result[i] = stringer.String()
+ }
+ return result
+}
+
+var PossibleSalCommands = map[readWriteModel.ApplicationId][]string{
+ readWriteModel.ApplicationId_RESERVED: nil, // TODO: Not yet implemented
+ readWriteModel.ApplicationId_FREE_USAGE: nil, // TODO: Not yet implemented
+ readWriteModel.ApplicationId_TEMPERATURE_BROADCAST: ms2s(readWriteModel.TemperatureBroadcastCommandTypeValues),
+ readWriteModel.ApplicationId_ROOM_CONTROL_SYSTEM: nil, // TODO: Not yet implemented
+ readWriteModel.ApplicationId_LIGHTING: ms2s(readWriteModel.LightingCommandTypeValues),
+ readWriteModel.ApplicationId_VENTILATION: ms2s(readWriteModel.LightingCommandTypeValues),
+ readWriteModel.ApplicationId_IRRIGATION_CONTROL: ms2s(readWriteModel.LightingCommandTypeValues),
+ readWriteModel.ApplicationId_POOLS_SPAS_PONDS_FOUNTAINS_CONTROL: ms2s(readWriteModel.LightingCommandTypeValues),
+ readWriteModel.ApplicationId_HEATING: ms2s(readWriteModel.LightingCommandTypeValues),
+ readWriteModel.ApplicationId_AIR_CONDITIONING: ms2s(readWriteModel.AirConditioningCommandTypeValues),
+ readWriteModel.ApplicationId_TRIGGER_CONTROL: ms2s(readWriteModel.TriggerControlCommandTypeValues),
+ readWriteModel.ApplicationId_ENABLE_CONTROL: ms2s(readWriteModel.EnableControlCommandTypeValues),
+ readWriteModel.ApplicationId_AUDIO_AND_VIDEO: ms2s(readWriteModel.LightingCommandTypeValues),
+ readWriteModel.ApplicationId_SECURITY: ms2s(readWriteModel.SecurityCommandTypeValues),
+ readWriteModel.ApplicationId_METERING: ms2s(readWriteModel.MeteringCommandTypeValues),
+ readWriteModel.ApplicationId_ACCESS_CONTROL: ms2s(readWriteModel.AccessControlCommandTypeValues),
+ readWriteModel.ApplicationId_CLOCK_AND_TIMEKEEPING: ms2s(readWriteModel.ClockAndTimekeepingCommandTypeValues),
+ readWriteModel.ApplicationId_TELEPHONY_STATUS_AND_CONTROL: ms2s(readWriteModel.TelephonyCommandTypeValues),
+ readWriteModel.ApplicationId_MEASUREMENT: ms2s(readWriteModel.MeasurementCommandTypeValues),
+ readWriteModel.ApplicationId_TESTING: nil, // TODO: Not yet implemented
+ readWriteModel.ApplicationId_MEDIA_TRANSPORT_CONTROL: ms2s(readWriteModel.MediaTransportControlCommandTypeValues),
+ readWriteModel.ApplicationId_ERROR_REPORTING: ms2s(readWriteModel.ErrorReportingCommandTypeValues),
+ readWriteModel.ApplicationId_HVAC_ACTUATOR: ms2s(readWriteModel.LightingCommandTypeValues),
+}
+
func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
if match := utils.GetSubgroupMatches(m.statusRequestPattern, query); match != nil {
var startingGroupAddressLabel *byte
@@ -200,6 +238,23 @@ func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
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
{
diff --git a/plc4go/internal/cbus/Subscriber.go b/plc4go/internal/cbus/Subscriber.go
index a61406a1e..aebaf1993 100644
--- a/plc4go/internal/cbus/Subscriber.go
+++ b/plc4go/internal/cbus/Subscriber.go
@@ -24,10 +24,11 @@ import (
"fmt"
apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
"github.com/apache/plc4x/plc4go/pkg/api/values"
- "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
+ readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
spiModel "github.com/apache/plc4x/plc4go/spi/model"
spiValues "github.com/apache/plc4x/plc4go/spi/values"
"github.com/rs/zerolog/log"
+ "strings"
"time"
)
@@ -78,10 +79,10 @@ func (m *Subscriber) Unsubscribe(ctx context.Context, unsubscriptionRequest apiM
return result
}
-func (m *Subscriber) handleMonitoredMMI(calReply model.CALReply) bool {
+func (m *Subscriber) handleMonitoredMMI(calReply readWriteModel.CALReply) bool {
var unitAddressString string
switch calReply := calReply.(type) {
- case model.CALReplyLongExactly:
+ case readWriteModel.CALReplyLongExactly:
if calReply.GetIsUnitAddress() {
unitAddressString = fmt.Sprintf("u%d", calReply.GetUnitAddress().GetAddress())
} else {
@@ -96,7 +97,6 @@ func (m *Subscriber) handleMonitoredMMI(calReply model.CALReply) bool {
unitAddressString = "u0" // On short form it should be always unit 0 TODO: double check that
}
calData := calReply.GetCalData()
- // TODO: filter
for _, subscriptionRequest := range m.subscriptionRequests {
fields := map[string]apiModel.PlcField{}
types := map[string]spiModel.SubscriptionType{}
@@ -114,7 +114,11 @@ func (m *Subscriber) handleMonitoredMMI(calReply model.CALReply) bool {
continue
}
if unitAddress := field.GetUnitAddress(); unitAddress != nil {
- // TODO: filter in unit address
+ unitSuffix := fmt.Sprintf("u%d", unitAddress.GetAddress())
+ if !strings.HasSuffix(unitAddressString, unitSuffix) {
+ log.Debug().Msgf("Current address string %s has not the suffix %s", unitAddressString, unitSuffix)
+ continue
+ }
}
application := field.GetApplication()
// TODO: filter in unit address
@@ -130,20 +134,70 @@ func (m *Subscriber) handleMonitoredMMI(calReply model.CALReply) bool {
var applicationString string
+ isLevel := true
+ blockStart := byte(0x0)
switch calData := calData.(type) {
- case model.CALDataStatusExactly:
+ case readWriteModel.CALDataStatusExactly:
applicationString = calData.GetApplication().ApplicationId().String()
- case model.CALDataStatusExtendedExactly:
+ blockStart = calData.GetBlockStart()
+
+ statusBytes := calData.GetStatusBytes()
+ responseCodes[fieldName] = apiModel.PlcResponseCode_OK
+ plcListValues := make([]values.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())
+ plcListValues[i*4+2] = spiValues.NewPlcSTRING(statusByte.GetGav2().String())
+ plcListValues[i*4+3] = spiValues.NewPlcSTRING(statusByte.GetGav3().String())
+ }
+ plcValues[fieldName] = spiValues.NewPlcList(plcListValues)
+ case readWriteModel.CALDataStatusExtendedExactly:
applicationString = calData.GetApplication().ApplicationId().String()
+ isLevel = calData.GetCoding() == readWriteModel.StatusCoding_LEVEL_BY_ELSEWHERE || calData.GetCoding() == readWriteModel.StatusCoding_LEVEL_BY_THIS_SERIAL_INTERFACE
+ blockStart = calData.GetBlockStart()
+ coding := calData.GetCoding()
+ switch coding {
+ case readWriteModel.StatusCoding_BINARY_BY_THIS_SERIAL_INTERFACE:
+ fallthrough
+ case readWriteModel.StatusCoding_BINARY_BY_ELSEWHERE:
+ statusBytes := calData.GetStatusBytes()
+ responseCodes[fieldName] = apiModel.PlcResponseCode_OK
+ plcListValues := make([]values.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())
+ plcListValues[i*4+2] = spiValues.NewPlcSTRING(statusByte.GetGav2().String())
+ plcListValues[i*4+3] = spiValues.NewPlcSTRING(statusByte.GetGav3().String())
+ }
+ plcValues[fieldName] = spiValues.NewPlcList(plcListValues)
+ case readWriteModel.StatusCoding_LEVEL_BY_THIS_SERIAL_INTERFACE:
+ fallthrough
+ case readWriteModel.StatusCoding_LEVEL_BY_ELSEWHERE:
+ levelInformation := calData.GetLevelInformation()
+ responseCodes[fieldName] = apiModel.PlcResponseCode_OK
+ plcListValues := make([]values.PlcValue, len(levelInformation))
+ for i, levelInformation := range levelInformation {
+ switch levelInformation := levelInformation.(type) {
+ case readWriteModel.LevelInformationAbsentExactly:
+ plcListValues[i] = spiValues.NewPlcSTRING("is absent")
+ case readWriteModel.LevelInformationCorruptedExactly:
+ plcListValues[i] = spiValues.NewPlcSTRING("corrupted")
+ case readWriteModel.LevelInformationNormalExactly:
+ plcListValues[i] = spiValues.NewPlcUSINT(levelInformation.GetActualLevel())
+ default:
+ panic("Impossible case")
+ }
+ }
+ plcValues[fieldName] = spiValues.NewPlcList(plcListValues)
+ }
default:
return false
}
- // TODO: we might need to encode more data into the address from sal data
- address[fieldName] = fmt.Sprintf("/%s/%s", unitAddressString, applicationString)
-
- // TODO: map values properly
- plcValues[fieldName] = spiValues.NewPlcSTRING(fmt.Sprintf("%s", calData))
- responseCodes[fieldName] = apiModel.PlcResponseCode_OK
+ statusType := "binary"
+ if isLevel {
+ statusType = fmt.Sprintf("level=0x%X", blockStart)
+ }
+ address[fieldName] = fmt.Sprintf("status/%s/%s", statusType, applicationString)
// Assemble a PlcSubscription event
if len(plcValues) > 0 {
@@ -156,7 +210,7 @@ func (m *Subscriber) handleMonitoredMMI(calReply model.CALReply) bool {
return true
}
-func (m *Subscriber) handleMonitoredSal(sal model.MonitoredSAL) bool {
+func (m *Subscriber) handleMonitoredSal(sal readWriteModel.MonitoredSAL) bool {
// TODO: filter
for _, subscriptionRequest := range m.subscriptionRequests {
fields := map[string]apiModel.PlcField{}
@@ -174,12 +228,6 @@ func (m *Subscriber) handleMonitoredSal(sal model.MonitoredSAL) bool {
plcValues[fieldName] = nil
continue
}
- if unitAddress := field.GetUnitAddress(); unitAddress != nil {
- // TODO: filter in unit address
- }
- application := field.GetApplication()
- // TODO: filter in unit address
- _ = application
subscriptionType := subscriptionRequest.GetType(fieldName)
// TODO: handle subscriptionType
@@ -189,10 +237,10 @@ func (m *Subscriber) handleMonitoredSal(sal model.MonitoredSAL) bool {
types[fieldName] = subscriptionRequest.GetType(fieldName)
intervals[fieldName] = subscriptionRequest.GetInterval(fieldName)
- var salData model.SALData
+ var salData readWriteModel.SALData
var unitAddressString, applicationString string
switch sal := sal.(type) {
- case model.MonitoredSALLongFormSmartModeExactly:
+ case readWriteModel.MonitoredSALLongFormSmartModeExactly:
if sal.GetIsUnitAddress() {
unitAddressString = fmt.Sprintf("u%d", sal.GetUnitAddress().GetAddress())
} else {
@@ -205,13 +253,28 @@ func (m *Subscriber) handleMonitoredSal(sal model.MonitoredSAL) bool {
}
applicationString = sal.GetApplication().ApplicationId().String()
salData = sal.GetSalData()
- case model.MonitoredSALShortFormBasicModeExactly:
+ case readWriteModel.MonitoredSALShortFormBasicModeExactly:
unitAddressString = "u0" // On short form it should be always unit 0 TODO: double check that
applicationString = sal.GetApplication().ApplicationId().String()
salData = sal.GetSalData()
}
- // TODO: we might need to encode more data into the address from sal data
- address[fieldName] = fmt.Sprintf("/%s/%s", unitAddressString, applicationString)
+ if unitAddress := field.GetUnitAddress(); unitAddress != nil {
+ unitSuffix := fmt.Sprintf("u%d", unitAddress.GetAddress())
+ if !strings.HasSuffix(unitAddressString, unitSuffix) {
+ log.Debug().Msgf("Current address string %s has not the suffix %s", unitAddressString, unitSuffix)
+ continue
+ }
+ }
+
+ if application := field.GetApplication(); application != readWriteModel.ApplicationIdContainer_RESERVED_FF {
+ if actualApplicationIdString := application.ApplicationId().String(); applicationString != actualApplicationIdString {
+ log.Debug().Msgf("Current application id %s doesn't matchactual id %s", unitAddressString, actualApplicationIdString)
+ continue
+ }
+ }
+
+ // TODO: we need to map commands e.g. if we get a MeteringDataElectricityConsumption we can map that to MeteringDataMeasureElectricity
+ address[fieldName] = fmt.Sprintf("sal/%s/%s", applicationString, "TODO")
// TODO: map values properly
plcValues[fieldName] = spiValues.NewPlcSTRING(fmt.Sprintf("%s", salData))
diff --git a/plc4go/internal/cbus/fieldtype_string.go b/plc4go/internal/cbus/fieldtype_string.go
index 0456cc67d..8e2831e00 100644
--- a/plc4go/internal/cbus/fieldtype_string.go
+++ b/plc4go/internal/cbus/fieldtype_string.go
@@ -29,14 +29,15 @@ func _() {
_ = x[CAL_RECALL-1]
_ = x[CAL_IDENTIFY-2]
_ = x[CAL_GETSTATUS-3]
- _ = x[SAL_MONITOR-4]
- _ = x[MMI_STATUS_MONITOR-5]
- _ = x[UNIT_INFO-6]
+ _ = x[SAL-4]
+ _ = x[SAL_MONITOR-5]
+ _ = x[MMI_STATUS_MONITOR-6]
+ _ = x[UNIT_INFO-7]
}
-const _FieldType_name = "STATUSCAL_RECALLCAL_IDENTIFYCAL_GETSTATUSSAL_MONITORMMI_STATUS_MONITORUNIT_INFO"
+const _FieldType_name = "STATUSCAL_RECALLCAL_IDENTIFYCAL_GETSTATUSSALSAL_MONITORMMI_STATUS_MONITORUNIT_INFO"
-var _FieldType_index = [...]uint8{0, 6, 16, 28, 41, 52, 70, 79}
+var _FieldType_index = [...]uint8{0, 6, 16, 28, 41, 44, 55, 73, 82}
func (i FieldType) String() string {
if i >= FieldType(len(_FieldType_index)-1) {