Skip to content

Commit

Permalink
feat: Implement the re-designed device profile model (#540)
Browse files Browse the repository at this point in the history
* feat: Implement the re-designed device profile model

- coreCommands is dropped from the device profile

- An attribute (isHidden: true/false with false the default) will be added to the deviceResource and deviceCommand elements to indicate the visibility of any resource or command via Core Command (effectively replacing the coreCommands section of the profile).

- On deviceCommands, we are removing the get/set section.

- On deviceCommands, we are renaming parameter to defaultValue

- On deviceCommands, we will use single resourceOperations to specify selected deviceResources on deviceCommands.

- On deviceCommands, add a **readWrite** indicator to show whether get or set for the deviceCommand is allowed.  The readWrite property can be more restrictive than the associated readWrite property for the deviceResource but it cannot reverse or relax the restriction of the deviceResource (example, make a deviceCommand ‘R’ when the deviceResource is ‘W’ or make the deviceCommand ‘RW’ when the deviceResource is ‘W’). From a schema perspective, the readWrite indicator will **align to what is already the readWrite property on deviceResource**.

Close #539

Signed-off-by: weichou <weichou1229@gmail.com>
  • Loading branch information
weichou1229 authored Mar 11, 2021
1 parent b029b98 commit 1c03d9d
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 205 deletions.
7 changes: 7 additions & 0 deletions v2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,10 @@ const (
REST = "REST"
MQTT = "MQTT"
)

// Constants for DeviceProfile
const (
ReadWrite_R = "R"
ReadWrite_W = "W"
ReadWrite_RW = "RW"
)
52 changes: 0 additions & 52 deletions v2/dtos/command.go

This file was deleted.

47 changes: 20 additions & 27 deletions v2/dtos/deviceCommand.go → v2/dtos/devicecommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,23 @@ import "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
// DeviceCommand and its properties are defined in the APIv2 specification:
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-metadata/2.x#/DeviceCommand
type DeviceCommand struct {
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Get []ResourceOperation `json:"get,omitempty" yaml:"get,omitempty" validate:"required_without=Set"`
Set []ResourceOperation `json:"set,omitempty" yaml:"set,omitempty" validate:"required_without=Get"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
IsHidden bool `json:"isHidden,omitempty" yaml:"isHidden,omitempty"`
ReadWrite string `json:"readWrite" yaml:"readWrite" validate:"required,oneof='R' 'W' 'RW'"`
ResourceOperations []ResourceOperation `json:"resourceOperations" yaml:"resourceOperations" validate:"gt=0,dive"`
}

// ToDeviceCommandModel transforms the DeviceCommand DTO to the DeviceCommand model
func ToDeviceCommandModel(p DeviceCommand) models.DeviceCommand {
getResourceOperations := make([]models.ResourceOperation, len(p.Get))
for i, ro := range p.Get {
getResourceOperations[i] = ToResourceOperationModel(ro)
func ToDeviceCommandModel(dto DeviceCommand) models.DeviceCommand {
resourceOperations := make([]models.ResourceOperation, len(dto.ResourceOperations))
for i, ro := range dto.ResourceOperations {
resourceOperations[i] = ToResourceOperationModel(ro)
}
setResourceOperations := make([]models.ResourceOperation, len(p.Set))
for i, ro := range p.Set {
setResourceOperations[i] = ToResourceOperationModel(ro)
}

return models.DeviceCommand{
Name: p.Name,
Get: getResourceOperations,
Set: setResourceOperations,
Name: dto.Name,
IsHidden: dto.IsHidden,
ReadWrite: dto.ReadWrite,
ResourceOperations: resourceOperations,
}
}

Expand All @@ -43,20 +40,16 @@ func ToDeviceCommandModels(deviceCommandDTOs []DeviceCommand) []models.DeviceCom
}

// FromDeviceCommandModelToDTO transforms the DeviceCommand model to the DeviceCommand DTO
func FromDeviceCommandModelToDTO(p models.DeviceCommand) DeviceCommand {
getResourceOperations := make([]ResourceOperation, len(p.Get))
for i, ro := range p.Get {
getResourceOperations[i] = FromResourceOperationModelToDTO(ro)
func FromDeviceCommandModelToDTO(d models.DeviceCommand) DeviceCommand {
resourceOperations := make([]ResourceOperation, len(d.ResourceOperations))
for i, ro := range d.ResourceOperations {
resourceOperations[i] = FromResourceOperationModelToDTO(ro)
}
setResourceOperations := make([]ResourceOperation, len(p.Set))
for i, ro := range p.Set {
setResourceOperations[i] = FromResourceOperationModelToDTO(ro)
}

return DeviceCommand{
Name: p.Name,
Get: getResourceOperations,
Set: setResourceOperations,
Name: d.Name,
IsHidden: d.IsHidden,
ReadWrite: d.ReadWrite,
ResourceOperations: resourceOperations,
}
}

Expand Down
50 changes: 16 additions & 34 deletions v2/dtos/deviceprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type DeviceProfile struct {
Labels []string `json:"labels,omitempty" yaml:"labels,flow,omitempty"`
DeviceResources []DeviceResource `json:"deviceResources" yaml:"deviceResources" validate:"required,gt=0,dive"`
DeviceCommands []DeviceCommand `json:"deviceCommands,omitempty" yaml:"deviceCommands,omitempty" validate:"dive"`
CoreCommands []Command `json:"coreCommands,omitempty" yaml:"coreCommands,omitempty" validate:"dive"`
}

// Validate satisfies the Validator interface
Expand All @@ -50,7 +49,6 @@ func (dp *DeviceProfile) UnmarshalYAML(unmarshal func(interface{}) error) error
Labels []string `yaml:"labels"`
DeviceResources []DeviceResource `yaml:"deviceResources"`
DeviceCommands []DeviceCommand `yaml:"deviceCommands"`
CoreCommands []Command `yaml:"coreCommands"`
}
if err := unmarshal(&alias); err != nil {
return errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to unmarshal request body as YAML.", err)
Expand Down Expand Up @@ -83,7 +81,6 @@ func ToDeviceProfileModel(deviceProfileDTO DeviceProfile) models.DeviceProfile {
Labels: deviceProfileDTO.Labels,
DeviceResources: ToDeviceResourceModels(deviceProfileDTO.DeviceResources),
DeviceCommands: ToDeviceCommandModels(deviceProfileDTO.DeviceCommands),
CoreCommands: ToCommandModels(deviceProfileDTO.CoreCommands),
}
}

Expand All @@ -99,7 +96,6 @@ func FromDeviceProfileModelToDTO(deviceProfile models.DeviceProfile) DeviceProfi
Labels: deviceProfile.Labels,
DeviceResources: FromDeviceResourceModelsToDTOs(deviceProfile.DeviceResources),
DeviceCommands: FromDeviceCommandModelsToDTOs(deviceProfile.DeviceCommands),
CoreCommands: FromCommandModelsToDTOs(deviceProfile.CoreCommands),
}
}

Expand All @@ -122,35 +118,18 @@ func ValidateDeviceProfileDTO(profile DeviceProfile) error {
}
dupCheck[command.Name] = true

// deviceResources referenced in deviceCommands must exist
getCommands := command.Get
for _, getCommand := range getCommands {
if !deviceResourcesContains(profile.DeviceResources, getCommand.DeviceResource) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's Get resource %s doesn't match any deivce resource", getCommand.DeviceResource), nil)
resourceOperations := command.ResourceOperations
for _, ro := range resourceOperations {
// ResourceOperations referenced in deviceCommands must exist
if !deviceResourcesContains(profile.DeviceResources, ro.DeviceResource) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's resource %s doesn't match any deivce resource", ro.DeviceResource), nil)
}
}
setCommands := command.Set
for _, setCommand := range setCommands {
if !deviceResourcesContains(profile.DeviceResources, setCommand.DeviceResource) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's Set resource %s doesn't match any deivce resource", setCommand.DeviceResource), nil)
// Check the ReadWrite whether is align to the deviceResource
if !validReadWritePermission(profile.DeviceResources, ro.DeviceResource, command.ReadWrite) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's ReadWrite permission '%s' doesn't align the deivce resource", command.ReadWrite), nil)
}
}
}
// coreCommands validation
dupCheck = make(map[string]bool)
for _, command := range profile.CoreCommands {
// coreCommand name should not duplicated
if dupCheck[command.Name] {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("core command %s is duplicated", command.Name), nil)
}
dupCheck[command.Name] = true

// coreCommands name should match the one of deviceResources and deviceCommands
if !deviceCommandsContains(profile.DeviceCommands, command.Name) &&
!deviceResourcesContains(profile.DeviceResources, command.Name) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("core command %s doesn't match any deivce command or resource", command.Name), nil)
}
}
return nil
}

Expand All @@ -165,13 +144,16 @@ func deviceResourcesContains(resources []DeviceResource, name string) bool {
return contains
}

func deviceCommandsContains(resources []DeviceCommand, name string) bool {
contains := false
func validReadWritePermission(resources []DeviceResource, name string, readWrite string) bool {
valid := true
for _, resource := range resources {
if resource.Name == name {
contains = true
break
if resource.Properties.ReadWrite != v2.ReadWrite_RW &&
resource.Properties.ReadWrite != readWrite {
valid = false
break
}
}
}
return contains
return valid
}
57 changes: 18 additions & 39 deletions v2/dtos/deviceprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,16 @@ var testDeviceProfile = models.DeviceProfile{
Attributes: testAttributes,
Properties: models.PropertyValue{
ValueType: v2.ValueTypeInt16,
ReadWrite: "RW",
ReadWrite: v2.ReadWrite_RW,
},
}},
DeviceCommands: []models.DeviceCommand{{
Name: TestDeviceCommandName,
Get: []models.ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
Set: []models.ResourceOperation{{
Name: TestDeviceCommandName,
ReadWrite: v2.ReadWrite_RW,
ResourceOperations: []models.ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
}},
CoreCommands: []models.Command{{
Name: TestDeviceCommandName,
Get: true,
Set: true,
}},
}

func profileData() DeviceProfile {
Expand All @@ -69,22 +62,15 @@ func profileData() DeviceProfile {
Attributes: testAttributes,
Properties: PropertyValue{
ValueType: v2.ValueTypeInt16,
ReadWrite: "RW",
ReadWrite: v2.ReadWrite_RW,
},
}},
DeviceCommands: []DeviceCommand{{
Name: TestDeviceCommandName,
Get: []ResourceOperation{{
Name: TestDeviceCommandName,
ReadWrite: v2.ReadWrite_RW,
ResourceOperations: []ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
Set: []ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
}},
CoreCommands: []Command{{
Name: TestDeviceCommandName,
Get: true,
Set: true,
}},
}
}
Expand All @@ -102,18 +88,11 @@ func TestValidateDeviceProfileDTO(t *testing.T) {
duplicatedDeviceCommand := profileData()
duplicatedDeviceCommand.DeviceCommands = append(
duplicatedDeviceCommand.DeviceCommands, DeviceCommand{Name: TestDeviceCommandName})
duplicatedCoreCommand := profileData()
duplicatedCoreCommand.CoreCommands = append(
duplicatedCoreCommand.CoreCommands, Command{Name: TestDeviceCommandName})
mismatchedCoreCommand := profileData()
mismatchedCoreCommand.CoreCommands = append(
mismatchedCoreCommand.CoreCommands, Command{Name: "missMatchedCoreCommand"})
mismatchedGetResource := profileData()
mismatchedGetResource.DeviceCommands[0].Get = append(
mismatchedGetResource.DeviceCommands[0].Get, ResourceOperation{DeviceResource: "missMatchedResource"})
mismatchedSetResource := profileData()
mismatchedSetResource.DeviceCommands[0].Set = append(
mismatchedSetResource.DeviceCommands[0].Set, ResourceOperation{DeviceResource: "missMatchedResource"})
mismatchedResource := profileData()
mismatchedResource.DeviceCommands[0].ResourceOperations = append(
mismatchedResource.DeviceCommands[0].ResourceOperations, ResourceOperation{DeviceResource: "missMatchedResource"})
invalidReadWrite := profileData()
invalidReadWrite.DeviceResources[0].Properties.ReadWrite = v2.ReadWrite_R

tests := []struct {
name string
Expand All @@ -123,10 +102,8 @@ func TestValidateDeviceProfileDTO(t *testing.T) {
{"valid device profile", valid, false},
{"duplicated device resource", duplicatedDeviceResource, true},
{"duplicated device command", duplicatedDeviceCommand, true},
{"duplicated core command", duplicatedCoreCommand, true},
{"mismatched core command", mismatchedCoreCommand, true},
{"mismatched Get resource", mismatchedGetResource, true},
{"mismatched Set resource", mismatchedSetResource, true},
{"mismatched resource", mismatchedResource, true},
{"invalid ReadWrite permission", invalidReadWrite, true},
}

for _, tt := range tests {
Expand All @@ -153,7 +130,8 @@ deviceResources:
deviceCommands:
-
name: "GenerateDeviceValue_Boolean_RW"
get:
readWrite: "RW"
resourceOperations:
- { deviceResource: "DeviceValue_Boolean_RW" }
`
inValid := `
Expand All @@ -166,6 +144,7 @@ deviceResources:
deviceCommands:
-
name: "GenerateDeviceValue_Boolean_RW"
readWrite: "RW",
get:
- { deviceResource: "DeviceValue_Boolean_RW" }
`
Expand Down
3 changes: 3 additions & 0 deletions v2/dtos/deviceresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
type DeviceResource struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
IsHidden bool `json:"isHidden,omitempty" yaml:"isHidden,omitempty"`
Tag string `json:"tag,omitempty" yaml:"tag,omitempty"`
Properties PropertyValue `json:"properties,omitempty" yaml:"properties,omitempty"`
Attributes map[string]string `json:"attributes,omitempty" yaml:"attributes,omitempty"`
Expand All @@ -22,6 +23,7 @@ func ToDeviceResourceModel(d DeviceResource) models.DeviceResource {
return models.DeviceResource{
Description: d.Description,
Name: d.Name,
IsHidden: d.IsHidden,
Tag: d.Tag,
Properties: ToPropertyValueModel(d.Properties),
Attributes: d.Attributes,
Expand All @@ -42,6 +44,7 @@ func FromDeviceResourceModelToDTO(d models.DeviceResource) DeviceResource {
return DeviceResource{
Description: d.Description,
Name: d.Name,
IsHidden: d.IsHidden,
Tag: d.Tag,
Properties: FromPropertyValueModelToDTO(d.Properties),
Attributes: d.Attributes,
Expand Down
Loading

0 comments on commit 1c03d9d

Please sign in to comment.