183 lines
6.1 KiB
Go
183 lines
6.1 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
//
|
|
// Copyright (C) 2018 Canonical Ltd
|
|
// Copyright (C) 2018-2023 IOTech Ltd
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
|
|
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/dtos"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/requests"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
|
|
|
|
"github.com/edgexfoundry/device-sdk-go/v4/internal/cache"
|
|
"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"
|
|
)
|
|
|
|
// processAsyncResults processes readings that are pushed from
|
|
// a DS implementation. Each is reading is optionally transformed
|
|
// before being pushed to Core Data.
|
|
// In this function, AsyncBufferSize is used to create a buffer for
|
|
// processing AsyncValues concurrently, so that events may arrive
|
|
// out-of-order in core-data / app service when AsyncBufferSize value
|
|
// is greater than or equal to two. Alternatively, we can process
|
|
// AsyncValues one by one in the same order by changing the AsyncBufferSize
|
|
// value to one.
|
|
func (s *deviceService) processAsyncResults(ctx context.Context, dic *di.Container) {
|
|
working := make(chan bool, s.config.Device.AsyncBufferSize)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case acv := <-s.asyncCh:
|
|
go s.sendAsyncValues(acv, working, dic)
|
|
}
|
|
}
|
|
}
|
|
|
|
// sendAsyncValues convert AsyncValues to event and send the event to CoreData
|
|
func (s *deviceService) sendAsyncValues(acv *sdkModels.AsyncValues, working chan bool, dic *di.Container) {
|
|
working <- true
|
|
defer func() {
|
|
<-working
|
|
}()
|
|
|
|
// Update the LastConnected metric in deviceCache
|
|
cache.Devices().SetLastConnectedByName(acv.DeviceName)
|
|
|
|
if len(acv.CommandValues) == 0 {
|
|
s.lc.Error("Skip sending AsyncValues because the CommandValues is empty.")
|
|
return
|
|
}
|
|
if len(acv.CommandValues) > 1 && acv.SourceName == "" {
|
|
s.lc.Error("Skip sending AsyncValues because the SourceName is empty.")
|
|
return
|
|
}
|
|
// We can use the first reading's DeviceResourceName as the SourceName
|
|
// when the CommandValues contains only one reading and the AsyncValues's SourceName is empty.
|
|
if len(acv.CommandValues) == 1 && acv.SourceName == "" {
|
|
acv.SourceName = acv.CommandValues[0].DeviceResourceName
|
|
}
|
|
|
|
configuration := container.ConfigurationFrom(dic.Get)
|
|
event, err := transformer.CommandValuesToEventDTO(acv.CommandValues, acv.DeviceName, acv.SourceName, configuration.Device.DataTransform, dic)
|
|
if err != nil {
|
|
s.lc.Errorf("failed to transform CommandValues to Event: %v", err)
|
|
return
|
|
}
|
|
|
|
common.SendEvent(event, "", dic)
|
|
}
|
|
|
|
// processAsyncFilterAndAdd filter and add devices discovered by
|
|
// device service protocol discovery.
|
|
func (s *deviceService) processAsyncFilterAndAdd(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case devices := <-s.deviceCh:
|
|
ctx := context.Background()
|
|
pws := cache.ProvisionWatchers().All()
|
|
for _, d := range devices {
|
|
for _, pw := range pws {
|
|
if pw.AdminState == models.Locked {
|
|
s.lc.Debugf("Skip th locked provision watcher %v", pw.Name)
|
|
continue
|
|
}
|
|
if s.checkAllowList(d, pw) && s.checkBlockList(d, pw) {
|
|
if _, ok := cache.Devices().ForName(d.Name); ok {
|
|
s.lc.Debugf("Candidate discovered device %s already existed", d.Name)
|
|
break
|
|
}
|
|
|
|
s.lc.Infof("Adding discovered device %s to Metadata", d.Name)
|
|
device := models.Device{
|
|
Name: d.Name,
|
|
Description: d.Description,
|
|
ProfileName: pw.DiscoveredDevice.ProfileName,
|
|
Protocols: d.Protocols,
|
|
Labels: d.Labels,
|
|
ServiceName: s.serviceKey,
|
|
AdminState: pw.DiscoveredDevice.AdminState,
|
|
OperatingState: models.Up,
|
|
AutoEvents: pw.DiscoveredDevice.AutoEvents,
|
|
Properties: pw.DiscoveredDevice.Properties,
|
|
}
|
|
|
|
req := requests.NewAddDeviceRequest(dtos.FromDeviceModelToDTO(device))
|
|
_, err := bootstrapContainer.DeviceClientFrom(s.dic.Get).Add(ctx, []requests.AddDeviceRequest{req})
|
|
if err != nil {
|
|
s.lc.Errorf("failed to create discovered device %s: %v", device.Name, err)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s.lc.Debug("Filtered device addition finished")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *deviceService) checkAllowList(d sdkModels.DiscoveredDevice, pw models.ProvisionWatcher) bool {
|
|
// ignore the device protocol properties name
|
|
for _, protocol := range d.Protocols {
|
|
matchedCount := 0
|
|
for name, regex := range pw.Identifiers {
|
|
if value, ok := protocol[name]; ok {
|
|
valueString := fmt.Sprintf("%v", value)
|
|
if valueString == "" {
|
|
s.lc.Debugf("Skipping identifier %s, cannot transform %s value '%v' to string type for discovered device %s", name, name, value, d.Name)
|
|
continue
|
|
}
|
|
matched, err := regexp.MatchString(regex, valueString)
|
|
if !matched || err != nil {
|
|
s.lc.Debugf("Discovered Device %s %s value '%v' did not match PW identifier: %s", d.Name, name, value, regex)
|
|
break
|
|
}
|
|
matchedCount += 1
|
|
}
|
|
}
|
|
// match succeed on all identifiers
|
|
if matchedCount == len(pw.Identifiers) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *deviceService) checkBlockList(d sdkModels.DiscoveredDevice, pw models.ProvisionWatcher) bool {
|
|
// a candidate should match none of the blocking identifiers
|
|
for name, blacklist := range pw.BlockingIdentifiers {
|
|
// ignore the device protocol properties name
|
|
for _, protocol := range d.Protocols {
|
|
if value, ok := protocol[name]; ok {
|
|
valueString := fmt.Sprintf("%v", value)
|
|
if valueString == "" {
|
|
s.lc.Debugf("Skipping identifier %s, cannot transform %s value '%v' to string type for discovered device %s", name, name, value, d.Name)
|
|
continue
|
|
}
|
|
for _, v := range blacklist {
|
|
if valueString == v {
|
|
s.lc.Debugf("Discovered Device %s %s value cannot be %v", d.Name, name, value)
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|