764 lines
29 KiB
Go
764 lines
29 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
//
|
|
// Copyright (C) 2020-2025 IOTech Ltd
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package application
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/http/utils"
|
|
|
|
"github.com/edgexfoundry/device-sdk-go/v4/internal/cache"
|
|
sdkCommon "github.com/edgexfoundry/device-sdk-go/v4/internal/common"
|
|
"github.com/edgexfoundry/device-sdk-go/v4/internal/container"
|
|
"github.com/edgexfoundry/device-sdk-go/v4/internal/transformer"
|
|
sdkModels "github.com/edgexfoundry/device-sdk-go/v4/pkg/models"
|
|
|
|
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/container"
|
|
"github.com/edgexfoundry/go-mod-bootstrap/v4/di"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/common"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/dtos"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/errors"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
|
|
)
|
|
|
|
func GetCommand(ctx context.Context, deviceName string, commandName string, queryParams string, regexCmd bool, dic *di.Container) (res *dtos.Event, err errors.EdgeX) {
|
|
if deviceName == "" {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "device name is empty", nil)
|
|
}
|
|
if commandName == "" {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "command is empty", nil)
|
|
}
|
|
var device models.Device
|
|
defer func() {
|
|
if err != nil {
|
|
DeviceRequestFailed(deviceName, dic)
|
|
} else {
|
|
DeviceRequestSucceeded(device, dic)
|
|
}
|
|
}()
|
|
|
|
device, err = validateServiceAndDeviceState(deviceName, dic)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
|
|
_, cmdExist := cache.Profiles().DeviceCommand(device.ProfileName, commandName)
|
|
if cmdExist {
|
|
res, err = readDeviceCommand(device, commandName, queryParams, dic)
|
|
} else if regexCmd {
|
|
res, err = readDeviceResourcesRegex(device, commandName, queryParams, dic)
|
|
} else {
|
|
res, err = readDeviceResource(device, commandName, queryParams, dic)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
|
|
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
|
|
lc.Debugf("GET Device Command successfully. Device: %s, Source: %s, %s: %s", deviceName, commandName, common.CorrelationHeader, utils.FromContext(ctx, common.CorrelationHeader))
|
|
|
|
cache.Devices().SetLastConnectedByName(deviceName)
|
|
return res, nil
|
|
}
|
|
|
|
func SetCommand(ctx context.Context, deviceName string, commandName string, queryParams string, requests map[string]any, dic *di.Container) (event *dtos.Event, err errors.EdgeX) {
|
|
if deviceName == "" {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "device name is empty", nil)
|
|
}
|
|
if commandName == "" {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "command is empty", nil)
|
|
}
|
|
var device models.Device
|
|
defer func() {
|
|
if err != nil {
|
|
DeviceRequestFailed(deviceName, dic)
|
|
} else {
|
|
DeviceRequestSucceeded(device, dic)
|
|
}
|
|
}()
|
|
|
|
device, err = validateServiceAndDeviceState(deviceName, dic)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
|
|
_, cmdExist := cache.Profiles().DeviceCommand(device.ProfileName, commandName)
|
|
if cmdExist {
|
|
event, err = writeDeviceCommand(device, commandName, queryParams, requests, dic)
|
|
} else {
|
|
event, err = writeDeviceResource(device, commandName, queryParams, requests, dic)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
|
|
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
|
|
lc.Debugf("SET Device Command successfully. Device: %s, Source: %s, %s: %s", deviceName, commandName, common.CorrelationHeader, utils.FromContext(ctx, common.CorrelationHeader))
|
|
|
|
cache.Devices().SetLastConnectedByName(deviceName)
|
|
return event, nil
|
|
}
|
|
|
|
func readDeviceResource(device models.Device, resourceName string, attributes string, dic *di.Container) (*dtos.Event, errors.EdgeX) {
|
|
dr, ok := cache.Profiles().DeviceResource(device.ProfileName, resourceName)
|
|
if !ok {
|
|
errMsg := fmt.Sprintf("DeviceResource %s not found", resourceName)
|
|
return nil, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, errMsg, nil)
|
|
}
|
|
// check deviceResource is not write-only
|
|
if dr.Properties.ReadWrite == common.ReadWrite_W {
|
|
errMsg := fmt.Sprintf("DeviceResource %s is marked as write-only", dr.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindNotAllowed, errMsg, nil)
|
|
}
|
|
|
|
var req sdkModels.CommandRequest
|
|
var reqs []sdkModels.CommandRequest
|
|
|
|
// prepare CommandRequest
|
|
req.DeviceResourceName = dr.Name
|
|
req.Attributes = dr.Attributes
|
|
if attributes != "" {
|
|
if len(req.Attributes) <= 0 {
|
|
req.Attributes = make(map[string]interface{})
|
|
}
|
|
req.Attributes[sdkCommon.URLRawQuery] = attributes
|
|
}
|
|
req.Type = dr.Properties.ValueType
|
|
reqs = append(reqs, req)
|
|
|
|
// execute protocol-specific read operation
|
|
driver := container.ProtocolDriverFrom(dic.Get)
|
|
results, err := driver.HandleReadCommands(device.Name, device.Protocols, reqs)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("error reading DeviceResource %s for %s", dr.Name, device.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
|
|
// convert CommandValue to Event
|
|
configuration := container.ConfigurationFrom(dic.Get)
|
|
event, err := transformer.CommandValuesToEventDTO(results, device.Name, dr.Name, configuration.Device.DataTransform, dic)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to convert CommandValue to Event", err)
|
|
}
|
|
|
|
return event, nil
|
|
}
|
|
|
|
func readDeviceResourcesRegex(device models.Device, regexResourceName string, attributes string, dic *di.Container) (*dtos.Event, errors.EdgeX) {
|
|
regex, err := regexp.CompilePOSIX(regexResourceName)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to CompilePOSIX resource name", err)
|
|
}
|
|
|
|
deviceResources, ok := cache.Profiles().DeviceResourcesByRegex(device.ProfileName, regex)
|
|
if !ok || len(deviceResources) == 0 {
|
|
errMsg := fmt.Sprintf("Regex DeviceResource %s not found", regexResourceName)
|
|
return nil, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, errMsg, nil)
|
|
}
|
|
|
|
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
|
|
reqs := make([]sdkModels.CommandRequest, 0)
|
|
for _, dr := range deviceResources {
|
|
// check deviceResource is not write-only
|
|
if dr.Properties.ReadWrite == common.ReadWrite_W {
|
|
lc.Debugf("DeviceResource %s is marked as write-only, skipping adding to RegEx Read list", dr.Name)
|
|
continue
|
|
}
|
|
|
|
// prepare CommandRequest
|
|
var req sdkModels.CommandRequest
|
|
req.DeviceResourceName = dr.Name
|
|
req.Attributes = dr.Attributes
|
|
if attributes != "" {
|
|
if len(req.Attributes) <= 0 {
|
|
req.Attributes = make(map[string]any)
|
|
}
|
|
req.Attributes[sdkCommon.URLRawQuery] = attributes
|
|
}
|
|
req.Type = dr.Properties.ValueType
|
|
|
|
reqs = append(reqs, req)
|
|
}
|
|
|
|
if len(reqs) == 0 {
|
|
errMsg := fmt.Sprintf("no readable resources matched with %s", regexResourceName)
|
|
return nil, errors.NewCommonEdgeX(errors.KindNotAllowed, errMsg, nil)
|
|
}
|
|
|
|
// execute protocol-specific read operation
|
|
driver := container.ProtocolDriverFrom(dic.Get)
|
|
results, err := driver.HandleReadCommands(device.Name, device.Protocols, reqs)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("error reading Regex DeviceResource(s) %s for %s", regexResourceName, device.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
|
|
// convert CommandValue to Event
|
|
configuration := container.ConfigurationFrom(dic.Get)
|
|
event, err := transformer.CommandValuesToEventDTO(results, device.Name, regexResourceName, configuration.Device.DataTransform, dic)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to convert CommandValue to Event", err)
|
|
}
|
|
|
|
return event, nil
|
|
}
|
|
|
|
func readDeviceCommand(device models.Device, commandName string, attributes string, dic *di.Container) (*dtos.Event, errors.EdgeX) {
|
|
dc, ok := cache.Profiles().DeviceCommand(device.ProfileName, commandName)
|
|
if !ok {
|
|
errMsg := fmt.Sprintf("DeviceCommand %s not found", commandName)
|
|
return nil, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, errMsg, nil)
|
|
}
|
|
// check deviceCommand is not write-only
|
|
if dc.ReadWrite == common.ReadWrite_W {
|
|
errMsg := fmt.Sprintf("DeviceCommand %s is marked as write-only", dc.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindNotAllowed, errMsg, nil)
|
|
}
|
|
// check ResourceOperation count does not exceed MaxCmdOps defined in configuration
|
|
configuration := container.ConfigurationFrom(dic.Get)
|
|
if len(dc.ResourceOperations) > configuration.Device.MaxCmdOps {
|
|
errMsg := fmt.Sprintf("GET command %s exceed device %s MaxCmdOps (%d)", dc.Name, device.Name, configuration.Device.MaxCmdOps)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, nil)
|
|
}
|
|
|
|
// prepare CommandRequests
|
|
reqs := make([]sdkModels.CommandRequest, len(dc.ResourceOperations))
|
|
for i, op := range dc.ResourceOperations {
|
|
drName := op.DeviceResource
|
|
// check the deviceResource in ResourceOperation actually exist
|
|
dr, ok := cache.Profiles().DeviceResource(device.ProfileName, drName)
|
|
if !ok {
|
|
errMsg := fmt.Sprintf("DeviceResource %s in GET commnd %s for %s not defined", drName, dc.Name, device.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, nil)
|
|
}
|
|
|
|
reqs[i].DeviceResourceName = dr.Name
|
|
reqs[i].Attributes = dr.Attributes
|
|
if attributes != "" {
|
|
if len(reqs[i].Attributes) <= 0 {
|
|
reqs[i].Attributes = make(map[string]interface{})
|
|
}
|
|
reqs[i].Attributes[sdkCommon.URLRawQuery] = attributes
|
|
}
|
|
reqs[i].Type = dr.Properties.ValueType
|
|
}
|
|
|
|
// execute protocol-specific read operation
|
|
driver := container.ProtocolDriverFrom(dic.Get)
|
|
results, err := driver.HandleReadCommands(device.Name, device.Protocols, reqs)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("error reading DeviceCommand %s for %s", dc.Name, device.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
|
|
// convert CommandValue to Event
|
|
event, err := transformer.CommandValuesToEventDTO(results, device.Name, dc.Name, configuration.Device.DataTransform, dic)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to transform CommandValue to Event", err)
|
|
}
|
|
|
|
return event, nil
|
|
}
|
|
|
|
func writeDeviceResource(device models.Device, resourceName string, attributes string, requests map[string]any, dic *di.Container) (*dtos.Event, errors.EdgeX) {
|
|
dr, ok := cache.Profiles().DeviceResource(device.ProfileName, resourceName)
|
|
if !ok {
|
|
errMsg := fmt.Sprintf("DeviceResource %s not found", resourceName)
|
|
return nil, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, errMsg, nil)
|
|
}
|
|
// check deviceResource is not read-only
|
|
if dr.Properties.ReadWrite == common.ReadWrite_R {
|
|
errMsg := fmt.Sprintf("DeviceResource %s is marked as read-only", dr.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindNotAllowed, errMsg, nil)
|
|
}
|
|
|
|
// check set parameters contains provided deviceResource
|
|
v, ok := requests[dr.Name]
|
|
if !ok {
|
|
if dr.Properties.DefaultValue != "" {
|
|
v = dr.Properties.DefaultValue
|
|
} else {
|
|
errMsg := fmt.Sprintf("DeviceResource %s not found in request body and no default value defined", dr.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, nil)
|
|
}
|
|
}
|
|
|
|
// create CommandValue
|
|
cv, edgexErr := createCommandValueFromDeviceResource(dr, v)
|
|
if edgexErr != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to create CommandValue", edgexErr)
|
|
}
|
|
|
|
// prepare CommandRequest
|
|
reqs := make([]sdkModels.CommandRequest, 1)
|
|
reqs[0].DeviceResourceName = cv.DeviceResourceName
|
|
reqs[0].Attributes = dr.Attributes
|
|
if attributes != "" {
|
|
if len(reqs[0].Attributes) <= 0 {
|
|
reqs[0].Attributes = make(map[string]any)
|
|
}
|
|
reqs[0].Attributes[sdkCommon.URLRawQuery] = attributes
|
|
}
|
|
reqs[0].Type = cv.Type
|
|
|
|
// transform write value
|
|
configuration := container.ConfigurationFrom(dic.Get)
|
|
if configuration.Device.DataTransform {
|
|
edgexErr = transformer.TransformWriteParameter(cv, dr.Properties)
|
|
if edgexErr != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to transform set parameter", edgexErr)
|
|
}
|
|
}
|
|
|
|
// execute protocol-specific write operation
|
|
driver := container.ProtocolDriverFrom(dic.Get)
|
|
err := driver.HandleWriteCommands(device.Name, device.Protocols, reqs, []*sdkModels.CommandValue{cv})
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("error writing DeviceResource %s for %s", dr.Name, device.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
|
|
// Updated resource value will be published to MessageBus as long as it's not write-only
|
|
if dr.Properties.ReadWrite != common.ReadWrite_W {
|
|
return transformer.CommandValuesToEventDTO([]*sdkModels.CommandValue{cv}, device.Name, resourceName, configuration.Device.DataTransform, dic)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func writeDeviceCommand(device models.Device, commandName string, attributes string, requests map[string]any, dic *di.Container) (*dtos.Event, errors.EdgeX) {
|
|
dc, ok := cache.Profiles().DeviceCommand(device.ProfileName, commandName)
|
|
if !ok {
|
|
errMsg := fmt.Sprintf("DeviceCommand %s not found", commandName)
|
|
return nil, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, errMsg, nil)
|
|
}
|
|
// check deviceCommand is not read-only
|
|
if dc.ReadWrite == common.ReadWrite_R {
|
|
errMsg := fmt.Sprintf("DeviceCommand %s is marked as read-only", dc.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindNotAllowed, errMsg, nil)
|
|
}
|
|
// check ResourceOperation count does not exceed MaxCmdOps defined in configuration
|
|
configuration := container.ConfigurationFrom(dic.Get)
|
|
if len(dc.ResourceOperations) > configuration.Device.MaxCmdOps {
|
|
errMsg := fmt.Sprintf("SET command %s exceed device %s MaxCmdOps (%d)", dc.Name, device.Name, configuration.Device.MaxCmdOps)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, nil)
|
|
}
|
|
|
|
// create CommandValues
|
|
cvs := make([]*sdkModels.CommandValue, 0, len(requests))
|
|
for _, ro := range dc.ResourceOperations {
|
|
drName := ro.DeviceResource
|
|
// check the deviceResource in ResourceOperation actually exist
|
|
dr, ok := cache.Profiles().DeviceResource(device.ProfileName, drName)
|
|
if !ok {
|
|
errMsg := fmt.Sprintf("DeviceResource %s in SET commnd %s for %s not defined", drName, dc.Name, device.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, nil)
|
|
}
|
|
|
|
// check request body contains the deviceResource
|
|
value, ok := requests[ro.DeviceResource]
|
|
if !ok {
|
|
if ro.DefaultValue != "" {
|
|
value = ro.DefaultValue
|
|
} else if dr.Properties.DefaultValue != "" {
|
|
value = dr.Properties.DefaultValue
|
|
} else {
|
|
errMsg := fmt.Sprintf("DeviceResource %s not found in request body and no default value defined", dr.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, nil)
|
|
}
|
|
}
|
|
|
|
// ResourceOperation mapping, notice that the order is opposite to get command mapping
|
|
// i.e. the mapping value is actually the key for set command.
|
|
if len(ro.Mappings) > 0 {
|
|
for k, v := range ro.Mappings {
|
|
if v == value {
|
|
value = k
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// create CommandValue
|
|
cv, err := createCommandValueFromDeviceResource(dr, value)
|
|
if err == nil {
|
|
cvs = append(cvs, cv)
|
|
} else {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to create CommandValue", err)
|
|
}
|
|
}
|
|
|
|
// prepare CommandRequests
|
|
reqs := make([]sdkModels.CommandRequest, len(cvs))
|
|
for i, cv := range cvs {
|
|
dr, _ := cache.Profiles().DeviceResource(device.ProfileName, cv.DeviceResourceName)
|
|
|
|
reqs[i].DeviceResourceName = cv.DeviceResourceName
|
|
reqs[i].Attributes = dr.Attributes
|
|
if attributes != "" {
|
|
if len(reqs[i].Attributes) <= 0 {
|
|
reqs[i].Attributes = make(map[string]interface{})
|
|
}
|
|
reqs[i].Attributes[sdkCommon.URLRawQuery] = attributes
|
|
}
|
|
reqs[i].Type = cv.Type
|
|
|
|
// transform write value
|
|
if configuration.Device.DataTransform {
|
|
err := transformer.TransformWriteParameter(cv, dr.Properties)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to transform set parameter", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// execute protocol-specific write operation
|
|
driver := container.ProtocolDriverFrom(dic.Get)
|
|
err := driver.HandleWriteCommands(device.Name, device.Protocols, reqs, cvs)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("error writing DeviceCommand %s for %s", dc.Name, device.Name)
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
|
|
// Updated resource(s) value will be published to MessageBus as long as they're not write-only
|
|
if dc.ReadWrite != common.ReadWrite_W {
|
|
return transformer.CommandValuesToEventDTO(cvs, device.Name, commandName, configuration.Device.DataTransform, dic)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func validateServiceAndDeviceState(deviceName string, dic *di.Container) (models.Device, errors.EdgeX) {
|
|
// check device service AdminState
|
|
ds := container.DeviceServiceFrom(dic.Get)
|
|
if ds.AdminState == models.Locked {
|
|
return models.Device{}, errors.NewCommonEdgeX(errors.KindServiceLocked, "service locked", nil)
|
|
}
|
|
|
|
// check requested device exists
|
|
device, ok := cache.Devices().ForName(deviceName)
|
|
if !ok {
|
|
return models.Device{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, fmt.Sprintf("device %s not found", deviceName), nil)
|
|
}
|
|
|
|
// check device's AdminState
|
|
if device.AdminState == models.Locked {
|
|
return models.Device{}, errors.NewCommonEdgeX(errors.KindServiceLocked, fmt.Sprintf("device %s locked", device.Name), nil)
|
|
}
|
|
// check device's OperatingState
|
|
// if it's a device return attempt, operating state is allowed to be DOWN
|
|
if device.OperatingState == models.Down {
|
|
err := errors.NewCommonEdgeX(errors.KindServiceLocked, fmt.Sprintf("device %s OperatingState is DOWN", device.Name), nil)
|
|
config := container.ConfigurationFrom(dic.Get)
|
|
if config.Device.AllowedFails == 0 || config.Device.DeviceDownTimeout == 0 {
|
|
return models.Device{}, err
|
|
}
|
|
reqFailsTracker := container.AllowedRequestFailuresTrackerFrom(dic.Get)
|
|
if reqFailsTracker.Value(deviceName) > 0 {
|
|
return models.Device{}, err
|
|
}
|
|
}
|
|
|
|
// check device's ProfileName
|
|
if device.ProfileName == "" {
|
|
return models.Device{}, errors.NewCommonEdgeX(errors.KindServiceLocked, "no associated device profile", nil)
|
|
}
|
|
|
|
return device, nil
|
|
}
|
|
|
|
func createCommandValueFromDeviceResource(dr models.DeviceResource, value interface{}) (*sdkModels.CommandValue, errors.EdgeX) {
|
|
if value == nil {
|
|
return &sdkModels.CommandValue{
|
|
DeviceResourceName: dr.Name,
|
|
Type: dr.Properties.ValueType,
|
|
Value: value,
|
|
Tags: make(map[string]string)}, nil
|
|
}
|
|
|
|
var err error
|
|
var result *sdkModels.CommandValue
|
|
|
|
v := fmt.Sprint(value)
|
|
|
|
if dr.Properties.ValueType != common.ValueTypeString && strings.TrimSpace(v) == "" {
|
|
return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("empty string is invalid for %v value type", dr.Properties.ValueType), nil)
|
|
}
|
|
|
|
switch dr.Properties.ValueType {
|
|
case common.ValueTypeString:
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeString, v)
|
|
case common.ValueTypeStringArray:
|
|
var arr []string
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeStringArray, arr)
|
|
case common.ValueTypeBool:
|
|
var value bool
|
|
value, err = strconv.ParseBool(v)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeBool, value)
|
|
case common.ValueTypeBoolArray:
|
|
var arr []bool
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeBoolArray, arr)
|
|
case common.ValueTypeUint8:
|
|
var n uint64
|
|
n, err = strconv.ParseUint(v, 10, 8)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint8, uint8(n))
|
|
case common.ValueTypeUint8Array:
|
|
var arr []uint8
|
|
strArr := strings.Split(strings.Trim(v, "[]"), ",")
|
|
for _, u := range strArr {
|
|
n, err := strconv.ParseUint(strings.Trim(u, " "), 10, 8)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
arr = append(arr, uint8(n))
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint8Array, arr)
|
|
case common.ValueTypeUint16:
|
|
var n uint64
|
|
n, err = strconv.ParseUint(v, 10, 16)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint16, uint16(n))
|
|
case common.ValueTypeUint16Array:
|
|
var arr []uint16
|
|
strArr := strings.Split(strings.Trim(v, "[]"), ",")
|
|
for _, u := range strArr {
|
|
n, err := strconv.ParseUint(strings.Trim(u, " "), 10, 16)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
arr = append(arr, uint16(n))
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint16Array, arr)
|
|
case common.ValueTypeUint32:
|
|
var n uint64
|
|
n, err = strconv.ParseUint(v, 10, 32)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint32, uint32(n))
|
|
case common.ValueTypeUint32Array:
|
|
var arr []uint32
|
|
strArr := strings.Split(strings.Trim(v, "[]"), ",")
|
|
for _, u := range strArr {
|
|
n, err := strconv.ParseUint(strings.Trim(u, " "), 10, 32)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
arr = append(arr, uint32(n))
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint32Array, arr)
|
|
case common.ValueTypeUint64:
|
|
var n uint64
|
|
n, err = strconv.ParseUint(v, 10, 64)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint64, n)
|
|
case common.ValueTypeUint64Array:
|
|
var arr []uint64
|
|
strArr := strings.Split(strings.Trim(v, "[]"), ",")
|
|
for _, u := range strArr {
|
|
n, err := strconv.ParseUint(strings.Trim(u, " "), 10, 64)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
arr = append(arr, n)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeUint64Array, arr)
|
|
case common.ValueTypeInt8:
|
|
var n int64
|
|
n, err = strconv.ParseInt(v, 10, 8)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt8, int8(n))
|
|
case common.ValueTypeInt8Array:
|
|
var arr []int8
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt8Array, arr)
|
|
case common.ValueTypeInt16:
|
|
var n int64
|
|
n, err = strconv.ParseInt(v, 10, 16)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt16, int16(n))
|
|
case common.ValueTypeInt16Array:
|
|
var arr []int16
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt16Array, arr)
|
|
case common.ValueTypeInt32:
|
|
var n int64
|
|
n, err = strconv.ParseInt(v, 10, 32)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt32, int32(n))
|
|
case common.ValueTypeInt32Array:
|
|
var arr []int32
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt32Array, arr)
|
|
case common.ValueTypeInt64:
|
|
var n int64
|
|
n, err = strconv.ParseInt(v, 10, 64)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt64, n)
|
|
case common.ValueTypeInt64Array:
|
|
var arr []int64
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeInt64Array, arr)
|
|
case common.ValueTypeFloat32:
|
|
var val float64
|
|
val, err = strconv.ParseFloat(v, 32)
|
|
if err == nil {
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeFloat32, float32(val))
|
|
break
|
|
}
|
|
if numError, ok := err.(*strconv.NumError); ok {
|
|
if numError.Err == strconv.ErrRange {
|
|
err = errors.NewCommonEdgeX(errors.KindServerError, "NumError", err)
|
|
break
|
|
}
|
|
}
|
|
var decodedToBytes []byte
|
|
decodedToBytes, err = base64.StdEncoding.DecodeString(v)
|
|
if err == nil {
|
|
var val float32
|
|
val, err = float32FromBytes(decodedToBytes)
|
|
if err != nil {
|
|
break
|
|
} else if math.IsNaN(float64(val)) {
|
|
err = fmt.Errorf("fail to parse %v to float32, unexpected result %v", v, val)
|
|
} else {
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeFloat32, val)
|
|
}
|
|
}
|
|
case common.ValueTypeFloat32Array:
|
|
var arr []float32
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeFloat32Array, arr)
|
|
case common.ValueTypeFloat64:
|
|
var val float64
|
|
val, err = strconv.ParseFloat(v, 64)
|
|
if err == nil {
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeFloat64, val)
|
|
break
|
|
}
|
|
if numError, ok := err.(*strconv.NumError); ok {
|
|
if numError.Err == strconv.ErrRange {
|
|
err = errors.NewCommonEdgeX(errors.KindServerError, "NumError", err)
|
|
break
|
|
}
|
|
}
|
|
var decodedToBytes []byte
|
|
decodedToBytes, err = base64.StdEncoding.DecodeString(v)
|
|
if err == nil {
|
|
val, err = float64FromBytes(decodedToBytes)
|
|
if err != nil {
|
|
break
|
|
} else if math.IsNaN(val) {
|
|
err = fmt.Errorf("fail to parse %v to float64, unexpected result %v", v, val)
|
|
} else {
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeFloat64, val)
|
|
}
|
|
}
|
|
case common.ValueTypeFloat64Array:
|
|
var arr []float64
|
|
err = json.Unmarshal([]byte(v), &arr)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("failed to convert set parameter %s to ValueType %s", v, dr.Properties.ValueType)
|
|
return result, errors.NewCommonEdgeX(errors.KindServerError, errMsg, err)
|
|
}
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeFloat64Array, arr)
|
|
case common.ValueTypeObject:
|
|
result, err = sdkModels.NewCommandValue(dr.Name, common.ValueTypeObject, value)
|
|
default:
|
|
err = errors.NewCommonEdgeX(errors.KindServerError, "unrecognized value type", nil)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func float32FromBytes(numericValue []byte) (res float32, err error) {
|
|
reader := bytes.NewReader(numericValue)
|
|
err = binary.Read(reader, binary.BigEndian, &res)
|
|
return
|
|
}
|
|
|
|
func float64FromBytes(numericValue []byte) (res float64, err error) {
|
|
reader := bytes.NewReader(numericValue)
|
|
err = binary.Read(reader, binary.BigEndian, &res)
|
|
return
|
|
}
|