508 lines
11 KiB
Go
508 lines
11 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
//
|
|
// Copyright (C) 2019-2023 IOTech Ltd
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package transformer
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/interfaces"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/common"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/errors"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
|
|
|
|
sdkCommon "github.com/edgexfoundry/device-sdk-go/v4/internal/common"
|
|
sdkModels "github.com/edgexfoundry/device-sdk-go/v4/pkg/models"
|
|
)
|
|
|
|
const (
|
|
defaultBase float64 = 0.0
|
|
defaultScale float64 = 1.0
|
|
defaultOffset float64 = 0.0
|
|
defaultMask uint64 = 0
|
|
defaultShift int64 = 0
|
|
|
|
Overflow = "overflow"
|
|
NaN = "NaN"
|
|
)
|
|
|
|
func TransformReadResult(cv *sdkModels.CommandValue, pv models.ResourceProperties) errors.EdgeX {
|
|
if !isNumericValueType(cv) {
|
|
return nil
|
|
}
|
|
res, err := isNaN(cv)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
} else if res {
|
|
errMSg := fmt.Sprintf("NaN error for DeviceResource %s", cv.DeviceResourceName)
|
|
return errors.NewCommonEdgeX(errors.KindNaNError, errMSg, nil)
|
|
}
|
|
|
|
value, err := commandValueForTransform(cv)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
newValue := value
|
|
|
|
if pv.Mask != nil && *pv.Mask != defaultMask &&
|
|
(cv.Type == common.ValueTypeUint8 || cv.Type == common.ValueTypeUint16 || cv.Type == common.ValueTypeUint32 || cv.Type == common.ValueTypeUint64) {
|
|
newValue, err = transformReadMask(newValue, *pv.Mask)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
}
|
|
if pv.Shift != nil && *pv.Shift != defaultShift &&
|
|
(cv.Type == common.ValueTypeUint8 || cv.Type == common.ValueTypeUint16 || cv.Type == common.ValueTypeUint32 || cv.Type == common.ValueTypeUint64) {
|
|
newValue, err = transformReadShift(newValue, *pv.Shift)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
}
|
|
if pv.Base != nil && *pv.Base != defaultBase {
|
|
newValue, err = transformBase(newValue, *pv.Base, true)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
}
|
|
if pv.Scale != nil && *pv.Scale != defaultScale {
|
|
newValue, err = transformScale(newValue, *pv.Scale, true)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
}
|
|
if pv.Offset != nil && *pv.Offset != defaultOffset {
|
|
newValue, err = transformOffset(newValue, *pv.Offset, true)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
}
|
|
|
|
if value != newValue {
|
|
cv.Value = newValue
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func transformBase(value any, base float64, read bool) (any, errors.EdgeX) {
|
|
var valueFloat64 float64
|
|
switch v := value.(type) {
|
|
case uint8:
|
|
valueFloat64 = float64(v)
|
|
case uint16:
|
|
valueFloat64 = float64(v)
|
|
case uint32:
|
|
valueFloat64 = float64(v)
|
|
case uint64:
|
|
valueFloat64 = float64(v)
|
|
case int8:
|
|
valueFloat64 = float64(v)
|
|
case int16:
|
|
valueFloat64 = float64(v)
|
|
case int32:
|
|
valueFloat64 = float64(v)
|
|
case int64:
|
|
valueFloat64 = float64(v)
|
|
case float32:
|
|
valueFloat64 = float64(v)
|
|
case float64:
|
|
valueFloat64 = v
|
|
}
|
|
|
|
if read {
|
|
valueFloat64 = math.Pow(base, valueFloat64)
|
|
} else {
|
|
valueFloat64 = math.Log(valueFloat64) / math.Log(base)
|
|
}
|
|
inRange := checkTransformedValueInRange(value, valueFloat64)
|
|
if !inRange {
|
|
errMsg := fmt.Sprintf("transformed value out of its original type (%T) range", value)
|
|
return 0, errors.NewCommonEdgeX(errors.KindOverflowError, errMsg, nil)
|
|
}
|
|
|
|
switch value.(type) {
|
|
case uint8:
|
|
value = uint8(valueFloat64)
|
|
case uint16:
|
|
value = uint16(valueFloat64)
|
|
case uint32:
|
|
value = uint32(valueFloat64)
|
|
case uint64:
|
|
value = uint64(valueFloat64)
|
|
case int8:
|
|
value = int8(valueFloat64)
|
|
case int16:
|
|
value = int16(valueFloat64)
|
|
case int32:
|
|
value = int32(valueFloat64)
|
|
case int64:
|
|
value = int64(valueFloat64)
|
|
case float32:
|
|
value = float32(valueFloat64)
|
|
case float64:
|
|
value = valueFloat64
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func transformScale(value any, scale float64, read bool) (any, errors.EdgeX) {
|
|
var valueFloat64 float64
|
|
switch v := value.(type) {
|
|
case uint8:
|
|
valueFloat64 = float64(v)
|
|
case uint16:
|
|
valueFloat64 = float64(v)
|
|
case uint32:
|
|
valueFloat64 = float64(v)
|
|
case uint64:
|
|
valueFloat64 = float64(v)
|
|
case int8:
|
|
valueFloat64 = float64(v)
|
|
case int16:
|
|
valueFloat64 = float64(v)
|
|
case int32:
|
|
valueFloat64 = float64(v)
|
|
case int64:
|
|
valueFloat64 = float64(v)
|
|
case float32:
|
|
valueFloat64 = float64(v)
|
|
case float64:
|
|
valueFloat64 = v
|
|
}
|
|
|
|
if read {
|
|
valueFloat64 = valueFloat64 * scale
|
|
} else {
|
|
valueFloat64 = valueFloat64 / scale
|
|
}
|
|
inRange := checkTransformedValueInRange(value, valueFloat64)
|
|
if !inRange {
|
|
errMsg := fmt.Sprintf("transformed value out of its original type (%T) range", value)
|
|
return 0, errors.NewCommonEdgeX(errors.KindOverflowError, errMsg, nil)
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case uint8:
|
|
if read {
|
|
value = v * uint8(scale)
|
|
} else {
|
|
value = v / uint8(scale)
|
|
}
|
|
case uint16:
|
|
if read {
|
|
value = v * uint16(scale)
|
|
} else {
|
|
value = v / uint16(scale)
|
|
}
|
|
case uint32:
|
|
if read {
|
|
value = v * uint32(scale)
|
|
} else {
|
|
value = v / uint32(scale)
|
|
}
|
|
case uint64:
|
|
if read {
|
|
value = v * uint64(scale)
|
|
} else {
|
|
value = v / uint64(scale)
|
|
}
|
|
case int8:
|
|
if read {
|
|
value = v * int8(scale)
|
|
} else {
|
|
value = v / int8(scale)
|
|
}
|
|
case int16:
|
|
if read {
|
|
value = v * int16(scale)
|
|
} else {
|
|
value = v / int16(scale)
|
|
}
|
|
case int32:
|
|
if read {
|
|
value = v * int32(scale)
|
|
} else {
|
|
value = v / int32(scale)
|
|
}
|
|
case int64:
|
|
if read {
|
|
value = v * int64(scale)
|
|
} else {
|
|
value = v / int64(scale)
|
|
}
|
|
case float32:
|
|
if read {
|
|
value = v * float32(scale)
|
|
} else {
|
|
value = v / float32(scale)
|
|
}
|
|
case float64:
|
|
if read {
|
|
value = v * scale
|
|
} else {
|
|
value = v / scale
|
|
}
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func transformOffset(value any, offset float64, read bool) (any, errors.EdgeX) {
|
|
var valueFloat64 float64
|
|
switch v := value.(type) {
|
|
case uint8:
|
|
valueFloat64 = float64(v)
|
|
case uint16:
|
|
valueFloat64 = float64(v)
|
|
case uint32:
|
|
valueFloat64 = float64(v)
|
|
case uint64:
|
|
valueFloat64 = float64(v)
|
|
case int8:
|
|
valueFloat64 = float64(v)
|
|
case int16:
|
|
valueFloat64 = float64(v)
|
|
case int32:
|
|
valueFloat64 = float64(v)
|
|
case int64:
|
|
valueFloat64 = float64(v)
|
|
case float32:
|
|
valueFloat64 = float64(v)
|
|
case float64:
|
|
valueFloat64 = v
|
|
}
|
|
|
|
if read {
|
|
valueFloat64 = valueFloat64 + offset
|
|
} else {
|
|
valueFloat64 = valueFloat64 - offset
|
|
}
|
|
inRange := checkTransformedValueInRange(value, valueFloat64)
|
|
if !inRange {
|
|
errMsg := fmt.Sprintf("transformed value out of its original type (%T) range", value)
|
|
return 0, errors.NewCommonEdgeX(errors.KindOverflowError, errMsg, nil)
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case uint8:
|
|
if read {
|
|
value = v + uint8(offset)
|
|
} else {
|
|
value = v - uint8(offset)
|
|
}
|
|
case uint16:
|
|
if read {
|
|
value = v + uint16(offset)
|
|
} else {
|
|
value = v - uint16(offset)
|
|
}
|
|
case uint32:
|
|
if read {
|
|
value = v + uint32(offset)
|
|
} else {
|
|
value = v - uint32(offset)
|
|
}
|
|
case uint64:
|
|
if read {
|
|
value = v + uint64(offset)
|
|
} else {
|
|
value = v - uint64(offset)
|
|
}
|
|
case int8:
|
|
if read {
|
|
value = v + int8(offset)
|
|
} else {
|
|
value = v - int8(offset)
|
|
}
|
|
case int16:
|
|
if read {
|
|
value = v + int16(offset)
|
|
} else {
|
|
value = v - int16(offset)
|
|
}
|
|
case int32:
|
|
if read {
|
|
value = v + int32(offset)
|
|
} else {
|
|
value = v - int32(offset)
|
|
}
|
|
case int64:
|
|
if read {
|
|
value = v + int64(offset)
|
|
} else {
|
|
value = v - int64(offset)
|
|
}
|
|
case float32:
|
|
if read {
|
|
value = v + float32(offset)
|
|
} else {
|
|
value = v - float32(offset)
|
|
}
|
|
case float64:
|
|
if read {
|
|
value = v + offset
|
|
} else {
|
|
value = v - offset
|
|
}
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func transformReadMask(value any, mask uint64) (any, errors.EdgeX) {
|
|
switch v := value.(type) {
|
|
case uint8:
|
|
value = v & uint8(mask)
|
|
case uint16:
|
|
value = v & uint16(mask)
|
|
case uint32:
|
|
value = v & uint32(mask)
|
|
case uint64:
|
|
value = v & mask
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
func transformReadShift(value any, shift int64) (any, errors.EdgeX) {
|
|
switch v := value.(type) {
|
|
case uint8:
|
|
if shift > 0 {
|
|
value = v << int8(shift)
|
|
} else {
|
|
value = v >> int8(-shift)
|
|
}
|
|
case uint16:
|
|
if shift > 0 {
|
|
value = v << int16(shift)
|
|
} else {
|
|
value = v >> int16(-shift)
|
|
}
|
|
case uint32:
|
|
if shift > 0 {
|
|
value = v << int32(shift)
|
|
} else {
|
|
value = v >> int32(-shift)
|
|
}
|
|
case uint64:
|
|
if shift > 0 {
|
|
value = v << shift
|
|
} else {
|
|
value = v >> (-shift)
|
|
}
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
func commandValueForTransform(cv *sdkModels.CommandValue) (interface{}, errors.EdgeX) {
|
|
if cv.Value == nil {
|
|
return nil, nil
|
|
}
|
|
var v interface{}
|
|
var err error
|
|
switch cv.Type {
|
|
case common.ValueTypeUint8:
|
|
v, err = cv.Uint8Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeUint16:
|
|
v, err = cv.Uint16Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeUint32:
|
|
v, err = cv.Uint32Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeUint64:
|
|
v, err = cv.Uint64Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeInt8:
|
|
v, err = cv.Int8Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeInt16:
|
|
v, err = cv.Int16Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeInt32:
|
|
v, err = cv.Int32Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeInt64:
|
|
v, err = cv.Int64Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeFloat32:
|
|
v, err = cv.Float32Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
case common.ValueTypeFloat64:
|
|
v, err = cv.Float64Value()
|
|
if err != nil {
|
|
return 0, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
default:
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, "unsupported ValueType for transformation", nil)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
func checkAssertion(
|
|
cv *sdkModels.CommandValue,
|
|
assertion string,
|
|
deviceName string,
|
|
lc logger.LoggingClient,
|
|
dc interfaces.DeviceClient) errors.EdgeX {
|
|
if assertion != "" && cv.ValueToString() != assertion {
|
|
go sdkCommon.UpdateOperatingState(deviceName, models.Down, lc, dc)
|
|
errMsg := fmt.Sprintf("Assertion failed for DeviceResource %s, with value %s", cv.DeviceResourceName, cv.ValueToString())
|
|
return errors.NewCommonEdgeX(errors.KindServerError, errMsg, nil)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mapCommandValue(value *sdkModels.CommandValue, mappings map[string]string) (*sdkModels.CommandValue, bool) {
|
|
var err error
|
|
var result *sdkModels.CommandValue
|
|
|
|
newValue, ok := mappings[value.ValueToString()]
|
|
if ok {
|
|
result, err = sdkModels.NewCommandValue(value.DeviceResourceName, common.ValueTypeString, newValue)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
}
|
|
return result, ok
|
|
}
|
|
|
|
func isNumericValueType(cv *sdkModels.CommandValue) bool {
|
|
switch cv.Type {
|
|
case common.ValueTypeUint8:
|
|
case common.ValueTypeUint16:
|
|
case common.ValueTypeUint32:
|
|
case common.ValueTypeUint64:
|
|
case common.ValueTypeInt8:
|
|
case common.ValueTypeInt16:
|
|
case common.ValueTypeInt32:
|
|
case common.ValueTypeInt64:
|
|
case common.ValueTypeFloat32:
|
|
case common.ValueTypeFloat64:
|
|
default:
|
|
return false
|
|
}
|
|
return true
|
|
}
|