Skip to content

Commit

Permalink
feat(plc4go/cbus): implemented value handler for writes
Browse files Browse the repository at this point in the history
  • Loading branch information
sruehl committed Aug 18, 2022
1 parent 86c4620 commit 96a452e
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 62 deletions.
22 changes: 8 additions & 14 deletions plc4go/internal/cbus/CBusMessageFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,14 @@ func FieldToCBusMessage(field model.PlcField, value values.PlcValue, alphaGenera
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
case
readWriteModel.ApplicationId_LIGHTING,
readWriteModel.ApplicationId_VENTILATION,
readWriteModel.ApplicationId_IRRIGATION_CONTROL,
readWriteModel.ApplicationId_POOLS_SPAS_PONDS_FOUNTAINS_CONTROL,
readWriteModel.ApplicationId_HEATING,
readWriteModel.ApplicationId_AUDIO_AND_VIDEO,
readWriteModel.ApplicationId_HVAC_ACTUATOR:
var lightingData readWriteModel.LightingData
switch salCommand {
case readWriteModel.LightingCommandType_OFF.PLC4XEnumName():
Expand Down Expand Up @@ -154,22 +160,12 @@ func FieldToCBusMessage(field model.PlcField, value values.PlcValue, alphaGenera
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:
Expand All @@ -188,8 +184,6 @@ func FieldToCBusMessage(field model.PlcField, value values.PlcValue, alphaGenera
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)
}
Expand Down
91 changes: 62 additions & 29 deletions plc4go/internal/cbus/FieldHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,19 @@ type FieldType uint8
//go:generate stringer -type FieldType
const (
STATUS FieldType = iota
// TODO: implement
CAL_RESET
CAL_RECALL
CAL_IDENTIFY
CAL_GETSTATUS
// TODO: implement
CAL_WRITE
// TODO: implement
CAL_IDENTIFY_REPLY
// TODO: implement
CAL_STATUS
// TODO: implement
CAL_STATUS_EXTENDED
SAL
SAL_MONITOR
MMI_STATUS_MONITOR
Expand All @@ -61,46 +71,44 @@ type FieldHandler struct {
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+))`),
calPattern: regexp.MustCompile(`^cal/(?P<unitAddress>.+)/(?P<calType>reset|recall=\[(?P<recallParamNo>\w+), ?(?P<recallCount>\d+)]|identify=(?P<identifyAttribute>\w+), ?(?P<getstatusCount>\d+)|getstatus=(?P<getstatusParamNo>\w+)|write=\[(?P<writeParamNo>\w+), ?(?P<writeCode>0[xX][0-9a-fA-F][0-9a-fA-F])]|identifyReply=(?P<replyAttribute>\w+)|reply=(?P<replyParamNo>\w+)|status=(?P<statusApplication>.*)|statusExtended=(?P<statusExtendedApplication>.*))`),
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
type CommandAndArgumentsCount interface {
fmt.Stringer
PLC4XEnumName() string
NumberOfArguments() uint8
}

var PossibleSalCommands = map[readWriteModel.ApplicationId][]string{
var PossibleSalCommands = map[readWriteModel.ApplicationId][]CommandAndArgumentsCount{
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_TEMPERATURE_BROADCAST: c2nl(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_LIGHTING: c2nl(readWriteModel.LightingCommandTypeValues),
readWriteModel.ApplicationId_VENTILATION: c2nl(readWriteModel.LightingCommandTypeValues),
readWriteModel.ApplicationId_IRRIGATION_CONTROL: c2nl(readWriteModel.LightingCommandTypeValues),
readWriteModel.ApplicationId_POOLS_SPAS_PONDS_FOUNTAINS_CONTROL: c2nl(readWriteModel.LightingCommandTypeValues),
readWriteModel.ApplicationId_HEATING: c2nl(readWriteModel.LightingCommandTypeValues),
readWriteModel.ApplicationId_AIR_CONDITIONING: c2nl(readWriteModel.AirConditioningCommandTypeValues),
readWriteModel.ApplicationId_TRIGGER_CONTROL: c2nl(readWriteModel.TriggerControlCommandTypeValues),
readWriteModel.ApplicationId_ENABLE_CONTROL: c2nl(readWriteModel.EnableControlCommandTypeValues),
readWriteModel.ApplicationId_AUDIO_AND_VIDEO: c2nl(readWriteModel.LightingCommandTypeValues),
readWriteModel.ApplicationId_SECURITY: c2nl(readWriteModel.SecurityCommandTypeValues),
readWriteModel.ApplicationId_METERING: c2nl(readWriteModel.MeteringCommandTypeValues),
readWriteModel.ApplicationId_ACCESS_CONTROL: c2nl(readWriteModel.AccessControlCommandTypeValues),
readWriteModel.ApplicationId_CLOCK_AND_TIMEKEEPING: c2nl(readWriteModel.ClockAndTimekeepingCommandTypeValues),
readWriteModel.ApplicationId_TELEPHONY_STATUS_AND_CONTROL: c2nl(readWriteModel.TelephonyCommandTypeValues),
readWriteModel.ApplicationId_MEASUREMENT: c2nl(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),
readWriteModel.ApplicationId_MEDIA_TRANSPORT_CONTROL: c2nl(readWriteModel.MediaTransportControlCommandTypeValues),
readWriteModel.ApplicationId_ERROR_REPORTING: c2nl(readWriteModel.ErrorReportingCommandTypeValues),
readWriteModel.ApplicationId_HVAC_ACTUATOR: c2nl(readWriteModel.LightingCommandTypeValues),
}

func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
Expand Down Expand Up @@ -169,6 +177,8 @@ func (m FieldHandler) handleCalPattern(match map[string]string) (model.PlcField,

calTypeArgument := match["calType"]
switch {
case strings.HasPrefix(calTypeArgument, "reset"):
panic("Not implemented") // TODO: implement me
case strings.HasPrefix(calTypeArgument, "recall="):
var recalParamNo readWriteModel.Parameter
recallParamNoArgument := match["recallParamNo"]
Expand Down Expand Up @@ -254,6 +264,16 @@ func (m FieldHandler) handleCalPattern(match map[string]string) (model.PlcField,
}
count = uint8(atoi)
return NewCALGetstatusField(unitAddress, recalParamNo, count, 1), nil
case strings.HasPrefix(calTypeArgument, "write="):
panic("Not implemented") // TODO: implement me
case strings.HasPrefix(calTypeArgument, "identifyReply="):
panic("Not implemented") // TODO: implement me
case strings.HasPrefix(calTypeArgument, "reply="):
panic("Not implemented") // TODO: implement me
case strings.HasPrefix(calTypeArgument, "status="):
panic("Not implemented") // TODO: implement me
case strings.HasPrefix(calTypeArgument, "statusExtended="):
panic("Not implemented") // TODO: implement me
default:
return nil, errors.Errorf("Invalid cal type %s", calTypeArgument)
}
Expand All @@ -269,13 +289,18 @@ func (m FieldHandler) handleSALPattern(match map[string]string) (model.PlcField,
return nil, errors.Wrap(err, "Error getting salCommand from argument")
}
isValid := false
numElements := uint16(0)
for _, request := range PossibleSalCommands[application.ApplicationId()] {
isValid = isValid || strings.HasPrefix(salCommand, request)
if salCommand == request.PLC4XEnumName() {
isValid = true
numElements = uint16(request.NumberOfArguments())
break
}
}
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 NewSALField(application, salCommand, numElements), nil
}

func (m FieldHandler) handleSALMonitorPattern(match map[string]string) (model.PlcField, error) {
Expand Down Expand Up @@ -490,3 +515,11 @@ func applicationIdFromArgument(applicationIdArgument string) (readWriteModel.App
return applicationIdByName, nil
}
}

func c2nl[T CommandAndArgumentsCount](t []T) []CommandAndArgumentsCount {
result := make([]CommandAndArgumentsCount, len(t))
for i, e := range t {
result[i] = e
}
return result
}
141 changes: 139 additions & 2 deletions plc4go/internal/cbus/ValueHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,150 @@
package cbus

import (
"github.com/apache/plc4x/plc4go/spi/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"
spiValues "github.com/apache/plc4x/plc4go/spi/values"
"github.com/pkg/errors"
"reflect"
)

type ValueHandler struct {
values.IEC61131ValueHandler
spiValues.IEC61131ValueHandler
}

func NewValueHandler() ValueHandler {
return ValueHandler{}
}

func (m ValueHandler) NewPlcValue(field apiModel.PlcField, value interface{}) (apiValues.PlcValue, error) {
switch field.GetTypeName() {
case
CAL_WRITE.GetName(),
CAL_IDENTIFY_REPLY.GetName(),
CAL_STATUS.GetName(),
CAL_STATUS_EXTENDED.GetName():
panic("implement me")
case SAL.GetName():
var curValues []any
if field.GetQuantity() > 1 {
s := reflect.ValueOf(value)
if s.Kind() != reflect.Slice {
return nil, errors.New("couldn't cast value to []interface{}")
}
curValues = make([]interface{}, s.Len())
for i := 0; i < s.Len(); i++ {
curValues[i] = s.Index(i).Interface()
}
} else {
curValues = append(curValues, value)
}

field := field.(*salField)
salCommand := field.salCommand
switch field.application.ApplicationId() {
case readWriteModel.ApplicationId_FREE_USAGE:
panic("Not yet implemented") // TODO: implement
case readWriteModel.ApplicationId_TEMPERATURE_BROADCAST:
switch salCommand {
case readWriteModel.TemperatureBroadcastCommandType_BROADCAST_EVENT.PLC4XEnumName():
if len(curValues) != 2 {
return nil, errors.Errorf("%s requires exactly 2 arguments [temperatureGroup,temperatureByte]", salCommand)
}
temperatureGroup, err := m.IEC61131ValueHandler.NewPlcValueFromType(spiValues.IEC61131_BYTE, curValues[0])
if err != nil {
return nil, errors.Wrap(err, "error creating value for temperatureGroup")
}
temperatureByte, err := m.IEC61131ValueHandler.NewPlcValueFromType(spiValues.IEC61131_BYTE, curValues[1])
if err != nil {
return nil, errors.Wrap(err, "error creating value for temperatureByte")
}
return spiValues.NewPlcList([]apiValues.PlcValue{temperatureGroup, temperatureByte}), nil
default:
return nil, errors.Errorf("Unsupported command %s for %s", salCommand, field.application.ApplicationId())
}
case readWriteModel.ApplicationId_ROOM_CONTROL_SYSTEM:
panic("Implement me")
case readWriteModel.ApplicationId_LIGHTING,
readWriteModel.ApplicationId_VENTILATION,
readWriteModel.ApplicationId_IRRIGATION_CONTROL,
readWriteModel.ApplicationId_POOLS_SPAS_PONDS_FOUNTAINS_CONTROL,
readWriteModel.ApplicationId_HEATING,
readWriteModel.ApplicationId_AUDIO_AND_VIDEO,
readWriteModel.ApplicationId_HVAC_ACTUATOR:
switch salCommand {
case readWriteModel.LightingCommandType_OFF.PLC4XEnumName():
if len(curValues) != 1 {
return nil, errors.Errorf("%s requires exactly 1 arguments [groupe]", salCommand)
}
group, err := m.IEC61131ValueHandler.NewPlcValueFromType(spiValues.IEC61131_BYTE, curValues[0])
if err != nil {
return nil, errors.Wrap(err, "error creating value for group")
}
return group, nil
case readWriteModel.LightingCommandType_ON.PLC4XEnumName():
if len(curValues) != 1 {
return nil, errors.Errorf("%s requires exactly 1 arguments [groupe]", salCommand)
}
group, err := m.IEC61131ValueHandler.NewPlcValueFromType(spiValues.IEC61131_BYTE, curValues[0])
if err != nil {
return nil, errors.Wrap(err, "error creating value for group")
}
return group, nil
case readWriteModel.LightingCommandType_RAMP_TO_LEVEL.PLC4XEnumName():
if len(curValues) != 2 {
return nil, errors.Errorf("%s requires exactly 2 arguments [group,level]", salCommand)
}
group, err := m.IEC61131ValueHandler.NewPlcValueFromType(spiValues.IEC61131_BYTE, curValues[0])
if err != nil {
return nil, errors.Wrap(err, "error creating value for group")
}
level, err := m.IEC61131ValueHandler.NewPlcValueFromType(spiValues.IEC61131_BYTE, curValues[0])
if err != nil {
return nil, errors.Wrap(err, "error creating value for level")
}
return spiValues.NewPlcList([]apiValues.PlcValue{group, level}), nil
case readWriteModel.LightingCommandType_TERMINATE_RAMP.PLC4XEnumName():
if len(curValues) != 1 {
return nil, errors.Errorf("%s requires exactly 1 arguments [groupe]", salCommand)
}
group, err := m.IEC61131ValueHandler.NewPlcValueFromType(spiValues.IEC61131_BYTE, curValues[0])
if err != nil {
return nil, errors.Wrap(err, "error creating value for group")
}
return group, nil
case readWriteModel.LightingCommandType_LABEL.PLC4XEnumName():
panic("Implement me")
default:
return nil, errors.Errorf("Unsupported command %s for %s", salCommand, field.application.ApplicationId())
}
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_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")
default:
return nil, errors.Errorf("No support for %s", field.application)
}
}
return m.IEC61131ValueHandler.NewPlcValue(field, value)
}
23 changes: 14 additions & 9 deletions plc4go/internal/cbus/fieldtype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 96a452e

Please sign in to comment.