Skip to content

Commit

Permalink
feat: Add new APIs to stop the device discovery/profile scan
Browse files Browse the repository at this point in the history
Signed-off-by: Ginny Guan <[email protected]>
  • Loading branch information
jinlinGuan committed Sep 3, 2024
1 parent 6fbfd52 commit bbccac4
Show file tree
Hide file tree
Showing 16 changed files with 431 additions and 105 deletions.
12 changes: 8 additions & 4 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The SDK will then filter these devices against pre-defined acceptance criteria (
A Provision Watcher contains the following fields:

`Identifiers`: A set of name-value pairs against which a new device's ProtocolProperties are matched
`BlockingIdentifiers`: An additional set of name-value pairs which if matched, will block the addition of a newly discovered device
`BlockingIdentifiers`: An additional set of name-value pairs that, if matched, will block the addition of a newly discovered device
`ProfileName`: The name of a DeviceProfile which should be assigned to new devices which meet the given criteria
`ServiceName`: The name of a DeviceService which the ProvisionWatcher should be applied on
`AdminState`: The initial Administrative State for new devices which meet the given criteria
Expand All @@ -36,14 +36,15 @@ Dynamic Device Discovery is triggered either by internal timer(see `Device/Disco
The following steps show how to trigger discovery on device-simple:
1. Set `Device/Discovery/Enabled` to true in [configuration file](cmd/device-simple/res/configuration.yaml)
2. Trigger discovery by sending POST request to DS endpoint: http://edgex-device-simple:59999/api/v3/discovery
3. `Simple-Device02` will be discovered and added to EdgeX.
3. `Simple-Device02` will be discovered and added to EdgeX, while `Simple-Device03` will be blocked by the [Provision Watcher `BlockingIdentifiers`](cmd/device-simple/res/provisionwatchers/Simple-Provision-Watcher.yml)

## ProfileScan
## Extended Protocol Driver
### ProfileScan
Some device protocols allow for devices to discover profiles automatically.
A Device Service may include a capability for discovering device profiles and creating the associated Device Profile objects for devices within EdgeX.

To enable profile scan, developers need to implement the [ProfileScan](../pkg/interfaces/protocolprofile.go) interface.
The `ProfileScan` interface defines a single `ProfileScan` method which is used to trigger device-specific profile scan.
The `ExtendedProtocolDriver` interface defines a `ProfileScan` method which is used to trigger device-specific profile scan.
The device profile found as a result of profile scan is returned to the SDK, and the SDK will then create the device profile and update the device to use the profile.

The following steps show how to trigger profile scan on device-simple:
Expand All @@ -56,3 +57,6 @@ The following steps show how to trigger profile scan on device-simple:
}
```
3. Device Profile `ProfileScan-Test-Profile` will be added to EdgeX and `ProfileScan-Simple-Device` will be updated to use the device profile.

### StopDeviceDiscovery and StopProfileScan
The `ExtendedProtocolDriver` interface defines a `StopDeviceDiscovery` to stop the device discovery and `StopProfileScan` to stop the profile scanning.
64 changes: 60 additions & 4 deletions example/driver/simpledriver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import (
"os"
"reflect"
"strconv"
"sync"
"time"

"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests"
"github.com/edgexfoundry/go-mod-core-contracts/v3/models"
gometrics "github.com/rcrowley/go-metrics"

Expand All @@ -36,6 +38,8 @@ const readCommandsExecutedName = "ReadCommandsExecuted"
type SimpleDriver struct {
sdk interfaces.DeviceServiceSDK
lc logger.LoggingClient
stopDiscovery stopDiscoveryState
stopProfileScan stopProfileScanState
asyncCh chan<- *sdkModels.AsyncValues
deviceCh chan<- []sdkModels.DiscoveredDevice
switchButton bool
Expand All @@ -48,6 +52,16 @@ type SimpleDriver struct {
serviceConfig *config.ServiceConfig
}

type stopDiscoveryState struct {
stop bool
locker sync.RWMutex
}

type stopProfileScanState struct {
stop map[string]bool
locker sync.RWMutex
}

func getImageBytes(imgFile string, buf *bytes.Buffer) error {
// Read existing image from file
img, err := os.Open(imgFile)
Expand Down Expand Up @@ -96,6 +110,7 @@ func (s *SimpleDriver) Initialize(sdk interfaces.DeviceServiceSDK) error {
"f2": 123,
}
s.stringArray = []string{"foo", "bar"}
s.stopProfileScan = stopProfileScanState{stop: make(map[string]bool)}

if err := sdk.LoadCustomConfig(s.serviceConfig, "SimpleCustom"); err != nil {
return fmt.Errorf("unable to load 'SimpleCustom' custom configuration: %s", err.Error())
Expand Down Expand Up @@ -319,9 +334,11 @@ func (s *SimpleDriver) Discover() error {
res := []sdkModels.DiscoveredDevice{device2}
time.Sleep(time.Duration(s.serviceConfig.SimpleCustom.Writable.DiscoverSleepDurationSecs) * time.Second)
s.sdk.PublishDeviceDiscoveryProgressSystemEvent(50, len(res), "")

time.Sleep(time.Duration(s.serviceConfig.SimpleCustom.Writable.DiscoverSleepDurationSecs) * time.Second)
res = append(res, device3)
defer s.setStopDeviceDiscovery(false)
if !s.getStopDeviceDiscovery() {
time.Sleep(time.Duration(s.serviceConfig.SimpleCustom.Writable.DiscoverSleepDurationSecs) * time.Second)
res = append(res, device3)
}
s.deviceCh <- res
return nil
}
Expand Down Expand Up @@ -353,9 +370,48 @@ func (s *SimpleDriver) ValidateDevice(device models.Device) error {
return nil
}

func (s *SimpleDriver) ProfileScan(payload sdkModels.ProfileScanRequest) (models.DeviceProfile, error) {
func (s *SimpleDriver) ProfileScan(payload requests.ProfileScanRequest) (models.DeviceProfile, error) {
time.Sleep(time.Duration(s.serviceConfig.SimpleCustom.Writable.DiscoverSleepDurationSecs) * time.Second)
s.sdk.PublishProfileScanProgressSystemEvent(payload.RequestId, 50, "")
if s.getStopProfileScan(payload.DeviceName) {
return models.DeviceProfile{}, fmt.Errorf("profile scanning is stopped")
}
time.Sleep(time.Duration(s.serviceConfig.SimpleCustom.Writable.DiscoverSleepDurationSecs) * time.Second)
return models.DeviceProfile{Name: payload.ProfileName}, nil
}

func (s *SimpleDriver) StopDeviceDiscovery(options map[string]any) {
s.lc.Debugf("StopDeviceDiscovery called: options=%v", options)
s.setStopDeviceDiscovery(true)
}

func (s *SimpleDriver) StopProfileScan(device string, options map[string]any) {
s.lc.Debugf("StopProfileScan called: options=%v", options)
s.setStopProfileScan(device, true)
}

func (s *SimpleDriver) getStopDeviceDiscovery() bool {
s.stopDiscovery.locker.RLock()
defer s.stopDiscovery.locker.RUnlock()
return s.stopDiscovery.stop
}

func (s *SimpleDriver) setStopDeviceDiscovery(stop bool) {
s.stopDiscovery.locker.Lock()
defer s.stopDiscovery.locker.Unlock()
s.stopDiscovery.stop = stop
s.lc.Debugf("set stopDeviceDiscovery to %v", stop)
}

func (s *SimpleDriver) getStopProfileScan(device string) bool {
s.stopProfileScan.locker.RLock()
defer s.stopProfileScan.locker.RUnlock()
return s.stopProfileScan.stop[device]
}

func (s *SimpleDriver) setStopProfileScan(device string, stop bool) {
s.stopProfileScan.locker.Lock()
defer s.stopProfileScan.locker.Unlock()
s.stopProfileScan.stop[device] = stop
s.lc.Debugf("set stopProfileScan to %v", stop)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21
require (
github.com/OneOfOne/xxhash v1.2.8
github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.52
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.32
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.43
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.31
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.52 h1:fv78Ky8/i3AOBOevstL
github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.52/go.mod h1:oOuvWXdu6YaB2J17pe4X0ey66AZFyTzOmAZDQxPGGmM=
github.com/edgexfoundry/go-mod-configuration/v3 v3.2.0-dev.12 h1:JGZ9fsyCZOgbNkg+qdW9JN63NKIEX95v5zJhCVdlp10=
github.com/edgexfoundry/go-mod-configuration/v3 v3.2.0-dev.12/go.mod h1:v7CvWGVmTh8dKItDNtfdBnYTeLhfZP5YmFiLsGJL9KU=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.32 h1:HJq0l/tElHS64T5Hw+NgaCkFa9h0zDyCRxNCs97DE/o=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.32/go.mod h1:d/FCa9Djq/pb7RYGEEhrR7fnKo+JK5IQ2YGW4LIHAqE=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.43 h1:/lMvMkSIXHnb5XBYr4O9GZM/nVdaKOsr7Q7I+RB9cwE=
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.43/go.mod h1:d/FCa9Djq/pb7RYGEEhrR7fnKo+JK5IQ2YGW4LIHAqE=
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.31 h1:mC0ZguoK8HjVxeD7dIiXRqKswM0y7gnPQJt1fLOh/v4=
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.31/go.mod h1:gcHtufkjd6oa3ZLqfzp66bCyCPx8MZe8Pwzh+2ITFnw=
github.com/edgexfoundry/go-mod-registry/v3 v3.2.0-dev.13 h1:LkaF2eOpSz4eUiGpah4a9r+cB/A0Pea3Nh7aTU9hlKs=
Expand Down
43 changes: 40 additions & 3 deletions internal/application/profilescan.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import (
"fmt"
"sync"

"github.com/edgexfoundry/device-sdk-go/v3/internal/cache"
"github.com/edgexfoundry/device-sdk-go/v3/internal/container"
"github.com/edgexfoundry/device-sdk-go/v3/internal/controller/http/correlation"
"github.com/edgexfoundry/device-sdk-go/v3/internal/utils"
"github.com/edgexfoundry/device-sdk-go/v3/pkg/interfaces"
sdkModels "github.com/edgexfoundry/device-sdk-go/v3/pkg/models"
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests"
"github.com/edgexfoundry/go-mod-core-contracts/v3/errors"
"github.com/edgexfoundry/go-mod-core-contracts/v3/models"
)

type profileScanLocker struct {
Expand All @@ -28,7 +31,7 @@ type profileScanLocker struct {

var locker = profileScanLocker{busyMap: make(map[string]bool)}

func ProfileScanWrapper(busy chan bool, ps interfaces.ProfileScan, req sdkModels.ProfileScanRequest, ctx context.Context, dic *di.Container) {
func ProfileScanWrapper(busy chan bool, extdriver interfaces.ExtendedProtocolDriver, req requests.ProfileScanRequest, ctx context.Context, dic *di.Container) {
locker.mux.Lock()
b := locker.busyMap[req.DeviceName]
busy <- b
Expand All @@ -49,7 +52,7 @@ func ProfileScanWrapper(busy chan bool, ps interfaces.ProfileScan, req sdkModels

utils.PublishProfileScanProgressSystemEvent(req.RequestId, 0, "", ctx, dic)
lc.Debugf("profile scan triggered with device name '%s' and profile name '%s', Correlation Id: %s", req.DeviceName, req.ProfileName, req.RequestId)
profile, err := ps.ProfileScan(req)
profile, err := extdriver.ProfileScan(req)
if err != nil {
errMsg := fmt.Sprintf("failed to trigger profile scan: %v, Correlation Id: %s", err.Error(), req.RequestId)
utils.PublishProfileScanProgressSystemEvent(req.RequestId, -1, errMsg, ctx, dic)
Expand Down Expand Up @@ -87,3 +90,37 @@ func releaseLock(deviceName string) {
locker.busyMap[deviceName] = false
locker.mux.Unlock()
}

func StopProfileScan(dic *di.Container, deviceName string, options map[string]any) errors.EdgeX {
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
extdriver := container.ExtendedProtocolDriverFrom(dic.Get)
if extdriver == nil {
return errors.NewCommonEdgeX(errors.KindNotImplemented, "Stop profile scan is not implemented", nil)
}

// check device service AdminState
ds := container.DeviceServiceFrom(dic.Get)
if ds.AdminState == models.Locked {
return errors.NewCommonEdgeX(errors.KindServiceLocked, "service locked", nil)
}

// check requested device exists
_, exist := cache.Devices().ForName(deviceName)
if !exist {
return errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, fmt.Sprintf("device %s not found", deviceName), nil)
}

locker.mux.Lock()
busy := locker.busyMap[deviceName]
locker.mux.Unlock()
if !busy {
lc.Debugf("no active profile scan process was found")
return nil
}

lc.Debugf("Stopping profile scan for device – %s", deviceName)
extdriver.StopProfileScan(deviceName, options)
lc.Debugf("Profile scan for device – %s stop signal is sent", deviceName)

return nil
}
25 changes: 25 additions & 0 deletions internal/autodiscovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/errors"
)

type discoveryLocker struct {
Expand Down Expand Up @@ -73,3 +74,27 @@ func DiscoveryWrapper(driver interfaces.ProtocolDriver, ctx context.Context, dic
})
locker.mux.Unlock()
}

func StopDeviceDiscovery(dic *di.Container, requestId string, options map[string]any) errors.EdgeX {
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
extdriver := container.ExtendedProtocolDriverFrom(dic.Get)
if extdriver == nil {
return errors.NewCommonEdgeX(errors.KindNotImplemented, "Stop device discovery is not implemented", nil)
}

workingId := container.DiscoveryRequestIdFrom(dic.Get)
if len(workingId) == 0 {
lc.Debugf("no active discovery process was found")
return nil
}
if len(requestId) != 0 && requestId != workingId {
lc.Debugf("failed to stop device discovery with request id %s: current working request id is %s", requestId, workingId)
return errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "There is no auto discovery process running with the requestId", nil)
}

lc.Debugf("Stopping device discovery – %s", workingId)
extdriver.StopDeviceDiscovery(options)
lc.Debugf("Device discovery – %s stop signal is sent", workingId)

return nil
}
5 changes: 0 additions & 5 deletions internal/common/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ const (
SDKReservedPrefix = "ds-"
)

const (
SystemEventActionDiscovery = "discovery"
SystemEventActionProfileScan = "profilescan"
)

// SDKVersion indicates the version of the SDK - will be overwritten by build
var SDKVersion string = "0.0.0"

Expand Down
16 changes: 10 additions & 6 deletions internal/container/deviceservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ var ProtocolDriverName = di.TypeInstanceToName((*interfaces.ProtocolDriver)(nil)
// AutoEventManagerName contains the name of autoevent manager implementation in the DIC
var AutoEventManagerName = di.TypeInstanceToName((*interfaces.AutoEventManager)(nil))

// ProfileScanName contains the name of profile scan implementation in the DIC.
var ProfileScanName = di.TypeInstanceToName((*interfaces.ProfileScan)(nil))
// ExtendedProtocolDriverName contains the name of extended protocol driver implementation in the DIC.
var ExtendedProtocolDriverName = di.TypeInstanceToName((*interfaces.ExtendedProtocolDriver)(nil))

// DeviceServiceFrom helper function queries the DIC and returns device service struct.
func DeviceServiceFrom(get di.Get) *models.DeviceService {
Expand All @@ -40,9 +40,9 @@ func AutoEventManagerFrom(get di.Get) interfaces.AutoEventManager {
return get(AutoEventManagerName).(interfaces.AutoEventManager)
}

// ProfileScanFrom helper function queries the DIC and returns profile scan implementation.
func ProfileScanFrom(get di.Get) interfaces.ProfileScan {
casted, ok := get(ProfileScanName).(interfaces.ProfileScan)
// ExtendedProtocolDriverFrom helper function queries the DIC and returns extended protocol driver implementation.
func ExtendedProtocolDriverFrom(get di.Get) interfaces.ExtendedProtocolDriver {
casted, ok := get(ExtendedProtocolDriverName).(interfaces.ExtendedProtocolDriver)
if ok {
return casted
}
Expand All @@ -54,5 +54,9 @@ var DiscoveryRequestIdName = di.TypeInstanceToName(new(string))

// DiscoveryRequestIdFrom helper function queries the DIC and returns discovery request id.
func DiscoveryRequestIdFrom(get di.Get) string {
return get(DiscoveryRequestIdName).(string)
id, ok := get(DiscoveryRequestIdName).(string)
if !ok {
return ""
}
return id
}
Loading

0 comments on commit bbccac4

Please sign in to comment.