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) {