| /* |
| * 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 values |
| |
| import ( |
| "errors" |
| "fmt" |
| "reflect" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/rs/zerolog" |
| |
| apiModel "github.com/apache/plc4x/plc4go/pkg/api/model" |
| apiValues "github.com/apache/plc4x/plc4go/pkg/api/values" |
| "github.com/apache/plc4x/plc4go/spi/options" |
| ) |
| |
| type DefaultValueHandler struct { |
| log zerolog.Logger |
| } |
| |
| func NewDefaultValueHandler(_options ...options.WithOption) DefaultValueHandler { |
| customLogger := options.ExtractCustomLoggerOrDefaultToGlobal(_options...) |
| return DefaultValueHandler{ |
| log: customLogger, |
| } |
| } |
| |
| func (m DefaultValueHandler) NewPlcValue(tag apiModel.PlcTag, value any) (apiValues.PlcValue, error) { |
| return m.parseType(tag, tag.GetArrayInfo(), value) |
| } |
| |
| func (m DefaultValueHandler) parseType(tag apiModel.PlcTag, arrayInfo []apiModel.ArrayInfo, value any) (apiValues.PlcValue, error) { |
| valueType := tag.GetValueType() |
| if (arrayInfo != nil) && (len(arrayInfo) > 0) { |
| return m.ParseListType(tag, arrayInfo, value) |
| } else if valueType == apiValues.Struct { |
| return m.ParseStructType(tag, value) |
| } |
| return m.ParseSimpleType(tag, value) |
| } |
| |
| func (m DefaultValueHandler) ParseListType(tag apiModel.PlcTag, arrayInfo []apiModel.ArrayInfo, value any) (apiValues.PlcValue, error) { |
| // We've reached the end of the recursion. |
| if len(arrayInfo) == 0 { |
| return m.parseType(tag, arrayInfo, value) |
| } |
| |
| s := reflect.ValueOf(value) |
| if s.Kind() != reflect.Slice { |
| return nil, errors.New("couldn't cast value to []any") |
| } |
| curValues := make([]any, s.Len()) |
| for i := 0; i < s.Len(); i++ { |
| curValues[i] = s.Index(i).Interface() |
| } |
| |
| curArrayInfo := arrayInfo[0] |
| restArrayInfo := arrayInfo[1:] |
| |
| // Check that the current slice has enough apiValues. |
| if len(curValues) != int(curArrayInfo.GetSize()) { |
| return nil, errors.New("number of actual apiValues " + strconv.Itoa(len(curValues)) + |
| " doesn't match tag size " + strconv.Itoa(int(curArrayInfo.GetSize()))) |
| } |
| |
| // Actually convert the current array info level. |
| var plcValues []apiValues.PlcValue |
| for i := uint32(0); i < curArrayInfo.GetSize(); i++ { |
| curValue := curValues[i] |
| plcValue, err := m.ParseListType(tag, restArrayInfo, curValue) |
| if err != nil { |
| return nil, errors.New("error parsing PlcValue: " + err.Error()) |
| } |
| plcValues = append(plcValues, plcValue) |
| } |
| return NewPlcList(plcValues), nil |
| } |
| |
| func (m DefaultValueHandler) ParseStructType(_ apiModel.PlcTag, _ any) (apiValues.PlcValue, error) { |
| return nil, errors.New("structured types not supported by the base value handler") |
| } |
| |
| func (m DefaultValueHandler) ParseSimpleType(tag apiModel.PlcTag, value any) (apiValues.PlcValue, error) { |
| plcValue, err := m.NewPlcValueFromType(tag.GetValueType(), value) |
| if err != nil && strings.HasPrefix(err.Error(), "couldn't cast") { |
| stringValue := fmt.Sprintf("%v", value) |
| plcValue, err = m.NewPlcValueFromType(tag.GetValueType(), stringValue) |
| if err == nil { |
| m.log.Debug(). |
| Interface("value", value). |
| Stringer("plcValue", plcValue). |
| Msg("had to convert %v into %v by using string conversion") |
| } |
| } |
| return plcValue, err |
| } |
| |
| func (m DefaultValueHandler) NewPlcValueFromType(valueType apiValues.PlcValueType, value any) (apiValues.PlcValue, error) { |
| // If the user passed in PLCValues, take a shortcut. |
| plcValue, isPlcValue := value.(apiValues.PlcValue) |
| if isPlcValue { |
| if plcValue.GetPlcValueType() != valueType { |
| // TODO: Check if the used PlcValueType can be casted to the target type. |
| } else if plcValue.GetPlcValueType() == apiValues.List { |
| // TODO: Check all items |
| } else if plcValue.GetPlcValueType() == apiValues.Struct { |
| // TODO: Check all children |
| } |
| return plcValue, nil |
| } |
| |
| stringValue, isString := value.(string) |
| switch valueType { |
| // Bit & Bit-Strings |
| case apiValues.BOOL: |
| if isString { |
| casted, err := strconv.ParseBool(stringValue) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to bool") |
| } |
| return NewPlcBOOL(casted), nil |
| } else { |
| casted, ok := value.(bool) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to bool") |
| } |
| return NewPlcBOOL(casted), nil |
| } |
| case apiValues.BYTE: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 8) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to byte") |
| } |
| return NewPlcBYTE(uint8(casted)), nil |
| } else { |
| casted, ok := value.(uint8) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint8") |
| } |
| return NewPlcBYTE(casted), nil |
| } |
| case apiValues.WORD: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 16) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to word") |
| } |
| return NewPlcWORD(uint16(casted)), nil |
| } else { |
| casted, ok := value.(uint16) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint16") |
| } |
| return NewPlcWORD(casted), nil |
| } |
| case apiValues.DWORD: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 32) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to dword") |
| } |
| return NewPlcDWORD(uint32(casted)), nil |
| } else { |
| casted, ok := value.(uint32) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint32") |
| } |
| return NewPlcDWORD(casted), nil |
| } |
| case apiValues.LWORD: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 64) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to lword") |
| } |
| return NewPlcLWORD(casted), nil |
| } else { |
| casted, ok := value.(uint64) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint64") |
| } |
| return NewPlcLWORD(casted), nil |
| } |
| |
| // Integers |
| case apiValues.USINT: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 8) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to usint") |
| } |
| return NewPlcUSINT(uint8(casted)), nil |
| } else { |
| casted, ok := value.(uint8) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint8") |
| } |
| return NewPlcUSINT(casted), nil |
| } |
| case apiValues.UINT: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 16) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to uint") |
| } |
| return NewPlcUINT(uint16(casted)), nil |
| } else { |
| casted, ok := value.(uint16) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint16") |
| } |
| return NewPlcUINT(casted), nil |
| } |
| case apiValues.UDINT: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 32) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to udint") |
| } |
| return NewPlcUDINT(uint32(casted)), nil |
| } else { |
| casted, ok := value.(uint32) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint32") |
| } |
| return NewPlcUDINT(casted), nil |
| } |
| case apiValues.ULINT: |
| if isString { |
| casted, err := strconv.ParseUint(stringValue, 10, 64) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to ulint") |
| } |
| return NewPlcULINT(casted), nil |
| } else { |
| casted, ok := value.(uint64) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to uint64") |
| } |
| return NewPlcULINT(casted), nil |
| } |
| case apiValues.SINT: |
| if isString { |
| casted, err := strconv.ParseInt(stringValue, 10, 8) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to sint") |
| } |
| return NewPlcSINT(int8(casted)), nil |
| } else { |
| casted, ok := value.(int8) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to int8") |
| } |
| return NewPlcSINT(casted), nil |
| } |
| case apiValues.INT: |
| if isString { |
| casted, err := strconv.ParseInt(stringValue, 10, 16) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to int") |
| } |
| return NewPlcINT(int16(casted)), nil |
| } else { |
| casted, ok := value.(int16) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to int16") |
| } |
| return NewPlcINT(casted), nil |
| } |
| case apiValues.DINT: |
| if isString { |
| casted, err := strconv.ParseInt(stringValue, 10, 32) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to dint") |
| } |
| return NewPlcDINT(int32(casted)), nil |
| } else { |
| casted, ok := value.(int32) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to int32") |
| } |
| return NewPlcDINT(casted), nil |
| } |
| case apiValues.LINT: |
| if isString { |
| casted, err := strconv.ParseInt(stringValue, 10, 64) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to lint") |
| } |
| return NewPlcLINT(casted), nil |
| } else { |
| casted, ok := value.(int64) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to int64") |
| } |
| return NewPlcLINT(casted), nil |
| } |
| |
| // Floating Point Values |
| case apiValues.REAL: |
| if isString { |
| casted, err := strconv.ParseFloat(stringValue, 32) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to real") |
| } |
| return NewPlcREAL(float32(casted)), nil |
| } else { |
| casted, ok := value.(float32) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to float32") |
| } |
| return NewPlcREAL(casted), nil |
| } |
| case apiValues.LREAL: |
| if isString { |
| casted, err := strconv.ParseFloat(stringValue, 64) |
| if err != nil { |
| return nil, errors.New("couldn't parse string value '" + stringValue + "' to lreal") |
| } |
| return NewPlcLREAL(casted), nil |
| } else { |
| casted, ok := value.(float64) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to float64") |
| } |
| return NewPlcLREAL(casted), nil |
| } |
| |
| // Temporal Values |
| // - Duration |
| case apiValues.TIME: |
| if isString { |
| return nil, errors.New("string to IEC61131_TIME conversion not implemented") |
| } else { |
| casted, ok := value.(time.Duration) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to time.Duration") |
| } |
| return NewPlcTIME(casted), nil |
| } |
| // - Date |
| case apiValues.DATE: |
| if isString { |
| return nil, errors.New("string to IEC61131_DATE conversion not implemented") |
| } else { |
| casted, ok := value.(time.Time) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to time.Time") |
| } |
| return NewPlcDATE(casted), nil |
| } |
| // - Time |
| case apiValues.TIME_OF_DAY: |
| if isString { |
| return nil, errors.New("string to IEC61131_TIME_OF_DAY conversion not implemented") |
| } else { |
| casted, ok := value.(time.Time) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to time.Time") |
| } |
| return NewPlcTIME_OF_DAY(casted), nil |
| } |
| // - Date and Time |
| case apiValues.DATE_AND_TIME: |
| if isString { |
| return nil, errors.New("string to IEC61131_DATE_AND_TIME conversion not implemented") |
| } else { |
| casted, ok := value.(time.Time) |
| if !ok { |
| return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to time.Time") |
| } |
| return NewPlcDATE_AND_TIME(casted), nil |
| } |
| |
| // Chars and Strings |
| case apiValues.CHAR: |
| if !isString { |
| return nil, errors.New("non-string to IEC61131_CHAR conversion not implemented") |
| } else if len(stringValue) > 1 { |
| return nil, errors.New("IEC61131_CHAR can only contain one character") |
| } else { |
| return NewPlcCHAR(stringValue), nil |
| } |
| case apiValues.WCHAR: |
| if !isString { |
| return nil, errors.New("non-string to IEC61131_WCHAR conversion not implemented") |
| } else if len(stringValue) > 1 { |
| return nil, errors.New("IEC61131_WCHAR can only contain one character") |
| } else { |
| return NewPlcWCHAR(stringValue), nil |
| } |
| case apiValues.STRING: |
| if !isString { |
| return nil, errors.New("non-string to IEC61131_STRING conversion not implemented") |
| } else { |
| return NewPlcSTRING(stringValue), nil |
| } |
| case apiValues.WSTRING: |
| if !isString { |
| return nil, errors.New("non-string to IEC61131_WSTRING conversion not implemented") |
| } else { |
| return NewPlcWSTRING(stringValue), nil |
| } |
| } |
| |
| return nil, errors.New("Unsupported type " + valueType.String()) |
| } |