文章
问答
冒泡
EdgeX Foundry之Device Service SDK源码刨析

DeviceService简介

DeviceService在Edgex Foundry中用于连接设备,他们直接与设备打交道,可以看作是设备的驱动吧。DeviceService作用的作用有:获取设备的状态;接收处理设备发过来的数据并发送到EdgeX;变更设备配置;设备发现。下面是基于Golang版本的Device Service SDK做一个主体流程的梳理说明。

核心流程节点

流程图

核心节点说明

首先说明一下,EdgeX Foundry中message bus下面涉及的相关的topic中使用的BaseTopic默认为edgex,是可以自定义的。

message bus其实很简单,消息体都是MessageEnvelope,大家看一下结构:

// MessageEnvelope is the data structure for messages. It wraps the generic message payload with attributes.
type MessageEnvelope struct {
	// ApiVersion (from Versionable) shows the API version for the message envelope.
	commonDTO.Versionable
	// ReceivedTopic is the topic that the message was received on.
	ReceivedTopic string `json:"receivedTopic"`
	// CorrelationID is an object id to identify the envelope.
	CorrelationID string `json:"correlationID"`
	// RequestID is an object id to identify the request.
	RequestID string `json:"requestID"`
	// ErrorCode provides the indication of error. '0' indicates no error, '1' indicates error.
	// Additional codes may be added in the future. If non-0, the payload will contain the error.
	ErrorCode int `json:"errorCode"`
	// Payload is byte representation of the data being transferred.
	Payload []byte `json:"payload"`
	// ContentType is the marshaled type of payload, i.e. application/json, application/xml, application/cbor, etc
	ContentType string `json:"contentType"`
	// QueryParams is optionally provided key/value pairs.
	QueryParams map[string]string `json:"queryParams,omitempty"`
}

读取配置,默认读取configuration.yaml

// loadConfigYamlFromFile attempts to read the specified configuration yaml file
func (cp *Processor) loadConfigYamlFromFile(yamlFile string) (map[string]any, error) {
	cp.lc.Infof("Loading configuration file from %s", yamlFile)
	contents, err := os.ReadFile(yamlFile)
	if err != nil {
		return nil, fmt.Errorf("failed to read configuration file %s: %s", yamlFile, err.Error())
	}

	data := make(map[string]any)

	err = yaml.Unmarshal(contents, &data)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshall configuration file %s: %s", yamlFile, err.Error())
	}
	return data, nil
}

这段代码是sdk中会从对应的yaml文件中读取配置为一个map结构。

如果需要,注册到注册中心

EdgeX Foundry使用的默认注册中心为Consul

初始化messageBus

messageBus默认提供四种实现方式:mqtt、redis、nats-core、nats-jetstream,默认使用的是redis,可以看一下对应的创建客户端代码片段:

// NewMessageClient is a factory function to instantiate different message client depending on
// the "Type" from the configuration
func NewMessageClient(msgConfig types.MessageBusConfig) (MessageClient, error) {

	if msgConfig.Broker.IsHostInfoEmpty() {
		return nil, fmt.Errorf("unable to create messageClient: Broker info not set")
	}

	switch lowerMsgType := strings.ToLower(msgConfig.Type); lowerMsgType {
	case MQTT:
		return mqtt.NewMQTTClient(msgConfig)
	case Redis:
		return redis.NewClient(msgConfig)
	case NatsCore:
		return nats.NewClient(msgConfig)
	case NatsJetStream:
		return jetstream.NewClient(msgConfig)
	default:
		return nil, fmt.Errorf("unknown message type '%s' requested", msgConfig.Type)
	}
}

订阅command核心模块的请求

DeviceService与command核心服务通信,大部分情况下是使用Message BUS进行交互的,可以看一下对应的TOPIC:

1)DeviceService订阅Command模块请求的TOPIC:{BaseTopic}/device/command/request/{deviceServiceName}/#,其中#的值为getset

2)DeviceService响应Command模块请求的TOPIC:{BaseTopic}/response/{deviceServiceName}/{requestId}

在处理逻辑中会根据TOPIC中最后一级标记的方法(getset)调用自定义驱动DriverHandleReadCommandsHandleWriteCommands方法。

放一段对应的处理逻辑代码片段:

// expected command response topic scheme: #/<service-name>/<device-name>/<command-name>/<method>
deviceName := topicLevels[length-3]
commandName, err := url.PathUnescape(topicLevels[length-2])
if err != nil {
    lc.Errorf("Failed to unescape command name '%s'", commandName)
    continue
}
method := topicLevels[length-1]

responsePublishTopic := common.BuildTopic(responsePublishTopicPrefix, msgEnvelope.RequestID)

switch strings.ToUpper(method) {
    case "GET":
    getCommand(ctx, msgEnvelope, responsePublishTopic, deviceName, commandName, dic)
    case "SET":
    setCommand(ctx, msgEnvelope, responsePublishTopic, deviceName, commandName, dic)
    default:
    lc.Errorf("unknown command method '%s', only 'get' or 'set' is allowed", method)
    continue
}

订阅metadata核心模块事件

1)DeviceService订阅Metadata模块请求的TOPIC:{BaseTopic}/system-events/core-metadata/+/+/{deviceServiceName}/#,如果使用了-i flag指定了不同的InstanceName,还会订阅:{BaseTopic}/system-events/core-metadata/provisionwatcher/+/{serviceBaseName}/#。如果不使用provisionwatcher这个特性的话,也是可以不订阅的,下面是关于针对这个topic生成的代码:

	// Must subscribe to provision watcher System Events separately when the service has an instance name. i.e. -i flag was used.
	// This is because Provision Watchers apply to all instances of the device service, i.e. the service base name (without the instance portion).
	// The above subscribe topic is for the specific instance name which is appropriate for Devices, Profiles and Devices service System Events
	// and when the -i flag is not used.
	if serviceBaseName != deviceService.Name {
		// Must replace the first wildcard with the type for Provision Watchers
		baseSubscribeTopic := strings.Replace(common.MetadataSystemEventSubscribeTopic, "+", common.ProvisionWatcherSystemEventType, 1)
		provisionWatcherSystemEventSubscribeTopic := common.BuildTopic(messageBusInfo.GetBaseTopicPrefix(),
			baseSubscribeTopic, serviceBaseName, "#")

		topics = append(topics, types.TopicChannel{
			Topic:    provisionWatcherSystemEventSubscribeTopic,
			Messages: messages,
		})

		lc.Infof("Subscribing additionally to Provision Watcher System Events on topic: %s", provisionWatcherSystemEventSubscribeTopic)
	}

我们再来看一下SystemEvent的定义,EdgeX Foundry中关于事件基本上都是使用这个结构体的:

// SystemEvent defines the data for a system event
type SystemEvent struct {
	common.Versionable `json:",inline"`
	Type               string            `json:"type"`
	Action             string            `json:"action"`
	Source             string            `json:"source"`
	Owner              string            `json:"owner"`
	Tags               map[string]string `json:"tags"`
	Details            any               `json:"details"`
	Timestamp          int64             `json:"timestamp"`
}

根据SystemEvent中的TypeAction确定用途:

1)设备相关(Device):新增设备(会调用DriverAddDevice方法)、更新设备(会调用DriverUpdateDevice方法)、删除设备(会调用DriverRemoveDevice方法)

2)设备模型相关(Device Profile):新增、更新、删除

3)驱动相关(Device Service):新增、更新、删除

4)provisionwatcher:新增、更新、删除

订阅设备验证事件

1)DeviceService订阅设备验证的请求TOPIC:{BaseTopic}/{deviceServiceName}/validate/device

2)DeviceService响应设备验证的请求的TOPIC:{BaseTopic}/response/{deviceServiceName}/{requestId}

实际调用Driver中的ValidateDevice方法。

初始化各个核心服务客户端实例

主要是各个核心服务的客户端,方便后继的调用,比如:CommandClient、DeviceClient、DeviceServiceClient、DeviceProfileClient等等。

// BootstrapHandler fulfills the BootstrapHandler contract.
// It creates instances of each of the EdgeX clients that are in the service's configuration and place them in the DIC.
// If the registry is enabled it will be used to get the URL for client otherwise it will use configuration for the url.
// This handler will fail if an unknown client is specified.
func (cb *ClientsBootstrap) BootstrapHandler(
	_ context.Context,
	_ *sync.WaitGroup,
	startupTimer startup.Timer,
	dic *di.Container) bool {

	lc := container.LoggingClientFrom(dic.Get)
	config := container.ConfigurationFrom(dic.Get)
	cb.registry = container.RegistryFrom(dic.Get)
	jwtSecretProvider := secret.NewJWTSecretProvider(container.SecretProviderExtFrom(dic.Get))

	if config.GetBootstrap().Clients != nil {
		for serviceKey, serviceInfo := range *config.GetBootstrap().Clients {
			var url string
			var err error

			if !serviceInfo.UseMessageBus {
				url, err = cb.getClientUrl(serviceKey, serviceInfo.Url(), startupTimer, lc)
				if err != nil {
					lc.Error(err.Error())
					return false
				}
			}

			switch serviceKey {
			case common.CoreDataServiceKey:
				dic.Update(di.ServiceConstructorMap{
					container.EventClientName: func(get di.Get) interface{} {
						return clients.NewEventClient(url, jwtSecretProvider)
					},
				})
			case common.CoreMetaDataServiceKey:
				dic.Update(di.ServiceConstructorMap{
					container.DeviceClientName: func(get di.Get) interface{} {
						return clients.NewDeviceClient(url, jwtSecretProvider)
					},
					container.DeviceServiceClientName: func(get di.Get) interface{} {
						return clients.NewDeviceServiceClient(url, jwtSecretProvider)
					},
					container.DeviceProfileClientName: func(get di.Get) interface{} {
						return clients.NewDeviceProfileClient(url, jwtSecretProvider)
					},
					container.ProvisionWatcherClientName: func(get di.Get) interface{} {
						return clients.NewProvisionWatcherClient(url, jwtSecretProvider)
					},
				})

			case common.CoreCommandServiceKey:
				var client interfaces.CommandClient

				if serviceInfo.UseMessageBus {
					// TODO: Move following outside loop when multiple messaging based clients exist
					messageClient := container.MessagingClientFrom(dic.Get)
					if messageClient == nil {
						lc.Errorf("Unable to create Command client using MessageBus: %s", "MessageBus Client was not created")
						return false
					}

					// TODO: Move following outside loop when multiple messaging based clients exist
					timeout, err := time.ParseDuration(config.GetBootstrap().Service.RequestTimeout)
					if err != nil {
						lc.Errorf("Unable to parse Service.RequestTimeout as a time duration: %v", err)
						return false
					}

					baseTopic := config.GetBootstrap().MessageBus.GetBaseTopicPrefix()
					client = clientsMessaging.NewCommandClient(messageClient, baseTopic, timeout)

					lc.Infof("Using messaging for '%s' clients", serviceKey)
				} else {
					client = clients.NewCommandClient(url, jwtSecretProvider)
				}

				dic.Update(di.ServiceConstructorMap{
					container.CommandClientName: func(get di.Get) interface{} {
						return client
					},
				})

			case common.SupportNotificationsServiceKey:
				dic.Update(di.ServiceConstructorMap{
					container.NotificationClientName: func(get di.Get) interface{} {
						return clients.NewNotificationClient(url, jwtSecretProvider)
					},
					container.SubscriptionClientName: func(get di.Get) interface{} {
						return clients.NewSubscriptionClient(url, jwtSecretProvider)
					},
				})

			case common.SupportSchedulerServiceKey:
				dic.Update(di.ServiceConstructorMap{
					container.IntervalClientName: func(get di.Get) interface{} {
						return clients.NewIntervalClient(url, jwtSecretProvider)
					},
					container.IntervalActionClientName: func(get di.Get) interface{} {
						return clients.NewIntervalActionClient(url, jwtSecretProvider)
					},
				})

			default:

			}
		}
	}
	return true
}

创建AutoEventManager

这个其实就是驱动相关设备属性可以自动上报的机制,比如你在配置设备的时候配置了属性autoEvents,配置的属性就会在这里进行执行。

deviceList:
  - name: virtualDevice001
    profileName: 1698998764550291457
    description: "虚拟设备Justin001"
    tags:
      productId: "1691704696665341954"
    protocols:
      other:
        address: 127.0.0.1
        port: 0000
    autoEvents:
      - interval: 10s
        onChange: false
        sourceName: WorkTemperature
      - interval: 10s
        onChange: false
        sourceName: EnvTemperature

初始化Rest API

其实就是初始化相关的HTTP接口服务,目前在EdgeX Foundry中的通信,大部分都转到了message Bus中了,不过在极个别的情况下,还是会使用HTTP的接口服务,比如在与Command模块会通过HTTP协议进行交互,对应的请求如下:

1)setCommand PUT /api/v3/device/name/{name}/{command}

2)getCommand GET /api/v3/device/name/{name}/{command}

func (c *RestController) InitRestRoutes() {
	c.lc.Info("Registering v2 routes...")
	// router.UseEncodedPath() tells the router to match the encoded original path to the routes
	c.router.UseEncodedPath()

	lc := container.LoggingClientFrom(c.dic.Get)
	secretProvider := container.SecretProviderExtFrom(c.dic.Get)
	authenticationHook := handlers.AutoConfigAuthenticationFunc(secretProvider, lc)

	// common
	c.addReservedRoute(common.ApiPingRoute, c.Ping).Methods(http.MethodGet)
	c.addReservedRoute(common.ApiVersionRoute, authenticationHook(c.Version)).Methods(http.MethodGet)
	c.addReservedRoute(common.ApiConfigRoute, authenticationHook(c.Config)).Methods(http.MethodGet)
	// secret
	c.addReservedRoute(common.ApiSecretRoute, authenticationHook(c.Secret)).Methods(http.MethodPost)
	// discovery
	c.addReservedRoute(common.ApiDiscoveryRoute, authenticationHook(c.Discovery)).Methods(http.MethodPost)
	// device command
	c.addReservedRoute(common.ApiDeviceNameCommandNameRoute, authenticationHook(c.GetCommand)).Methods(http.MethodGet)
	c.addReservedRoute(common.ApiDeviceNameCommandNameRoute, authenticationHook(c.SetCommand)).Methods(http.MethodPut)

	c.router.Use(correlation.ManageHeader)
	c.router.Use(correlation.LoggingMiddleware(c.lc))
	c.router.Use(correlation.UrlDecodeMiddleware(c.lc))
}

驱动初始化

调用驱动服务方法Initialize进行驱动的初始化操作。

type ProtocolDriver interface {
	// Initialize performs protocol-specific initialization for the device service.
	// The given *AsyncValues channel can be used to push asynchronous events and
	// readings to Core Data. The given []DiscoveredDevice channel is used to send
	// discovered devices that will be filtered and added to Core Metadata asynchronously.
	Initialize(sdk DeviceServiceSDK) error
	
	......
}

注册到edgex foundry的Metadata模块

设备服务自注册到EdgeX Foundry中。

	edgexErr = s.selfRegister()
	if edgexErr != nil {
		s.lc.Errorf("Failed to register %s on Metadata: %s", s.serviceKey, edgexErr.Error())
		return false
	}

加载本地device profile配置文件

加载本地配置的device profile文件:

package provision

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	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/google/uuid"
	"gopkg.in/yaml.v3"

	"github.com/edgexfoundry/device-sdk-go/v3/internal/cache"
)

const (
	jsonExt = ".json"
	yamlExt = ".yaml"
	ymlExt  = ".yml"
)

func LoadProfiles(path string, dic *di.Container) errors.EdgeX {
	if path == "" {
		return nil
	}

	absPath, err := filepath.Abs(path)
	if err != nil {
		return errors.NewCommonEdgeX(errors.KindServerError, "failed to create absolute path", err)
	}

	files, err := os.ReadDir(absPath)
	if err != nil {
		return errors.NewCommonEdgeX(errors.KindServerError, "failed to read directory", err)
	}

	if len(files) == 0 {
		return nil
	}

	lc := bootstrapContainer.LoggingClientFrom(dic.Get)
	lc.Infof("Loading pre-defined profiles from %s(%d files found)", absPath, len(files))

	var addProfilesReq []requests.DeviceProfileRequest
	dpc := bootstrapContainer.DeviceProfileClientFrom(dic.Get)
	for _, file := range files {
		var profile dtos.DeviceProfile
		fullPath := filepath.Join(absPath, file.Name())
		if strings.HasSuffix(fullPath, yamlExt) || strings.HasSuffix(fullPath, ymlExt) {
			content, err := os.ReadFile(fullPath)
			if err != nil {
				lc.Errorf("Failed to read %s: %v", fullPath, err)
				continue
			}

			err = yaml.Unmarshal(content, &profile)
			if err != nil {
				lc.Errorf("Failed to YAML decode profile %s: %v", file.Name(), err)
				continue
			}
		} else if strings.HasSuffix(fullPath, jsonExt) {
			content, err := os.ReadFile(fullPath)
			if err != nil {
				lc.Errorf("Failed to read %s: %v", fullPath, err)
				continue
			}

			err = json.Unmarshal(content, &profile)
			if err != nil {
				lc.Errorf("Failed to JSON decode profile %s: %v", file.Name(), err)
				continue
			}
		} else {
			continue
		}

		res, err := dpc.DeviceProfileByName(context.Background(), profile.Name)
		if err == nil {
			lc.Infof("Profile %s exists, using the existing one", profile.Name)
			_, exist := cache.Profiles().ForName(profile.Name)
			if !exist {
				err = cache.Profiles().Add(dtos.ToDeviceProfileModel(res.Profile))
				if err != nil {
					return errors.NewCommonEdgeX(errors.KindServerError, fmt.Sprintf("failed to cache the profile %s", res.Profile.Name), err)
				}
			}
		} else {
			lc.Infof("Profile %s not found in Metadata, adding it ...", profile.Name)
			req := requests.NewDeviceProfileRequest(profile)
			addProfilesReq = append(addProfilesReq, req)
		}
	}

	if len(addProfilesReq) == 0 {
		return nil
	}
	ctx := context.WithValue(context.Background(), common.CorrelationHeader, uuid.NewString()) // nolint:staticcheck
	_, edgexErr := dpc.Add(ctx, addProfilesReq)
	return edgexErr
}

加载本地设备配置文件

加载本地配置的设备文件:

package provision

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	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"
	"github.com/google/uuid"
	"github.com/hashicorp/go-multierror"
	"gopkg.in/yaml.v3"

	"github.com/edgexfoundry/device-sdk-go/v3/internal/cache"
	"github.com/edgexfoundry/device-sdk-go/v3/internal/container"
)

func LoadDevices(path string, dic *di.Container) errors.EdgeX {
	if path == "" {
		return nil
	}

	absPath, err := filepath.Abs(path)
	if err != nil {
		return errors.NewCommonEdgeX(errors.KindServerError, "failed to create absolute path", err)
	}

	files, err := os.ReadDir(absPath)
	if err != nil {
		return errors.NewCommonEdgeX(errors.KindServerError, "failed to read directory", err)
	}

	if len(files) == 0 {
		return nil
	}

	lc := bootstrapContainer.LoggingClientFrom(dic.Get)
	lc.Infof("Loading pre-defined devices from %s(%d files found)", absPath, len(files))

	var addDevicesReq []requests.AddDeviceRequest
	serviceName := container.DeviceServiceFrom(dic.Get).Name
	for _, file := range files {
		var devices []dtos.Device
		fullPath := filepath.Join(absPath, file.Name())
		if strings.HasSuffix(fullPath, yamlExt) || strings.HasSuffix(fullPath, ymlExt) {
			content, err := os.ReadFile(fullPath)
			if err != nil {
				lc.Errorf("Failed to read %s: %v", fullPath, err)
				continue
			}
			d := struct {
				DeviceList []dtos.Device `yaml:"deviceList"`
			}{}
			err = yaml.Unmarshal(content, &d)
			if err != nil {
				lc.Errorf("Failed to YAML decode %s: %v", fullPath, err)
				continue
			}
			devices = d.DeviceList
		} else if strings.HasSuffix(fullPath, ".json") {
			content, err := os.ReadFile(fullPath)
			if err != nil {
				lc.Errorf("Failed to read %s: %v", fullPath, err)
				continue
			}
			err = json.Unmarshal(content, &devices)
			if err != nil {
				lc.Errorf("Failed to JSON decode %s: %v", fullPath, err)
				continue
			}
		} else {
			continue
		}

		for _, device := range devices {
			if _, ok := cache.Devices().ForName(device.Name); ok {
				lc.Infof("Device %s exists, using the existing one", device.Name)
			} else {
				lc.Infof("Device %s not found in Metadata, adding it ...", device.Name)
				device.ServiceName = serviceName
				device.AdminState = models.Unlocked
				device.OperatingState = models.Up
				req := requests.NewAddDeviceRequest(device)
				addDevicesReq = append(addDevicesReq, req)
			}
		}
	}

	if len(addDevicesReq) == 0 {
		return nil
	}
	dc := bootstrapContainer.DeviceClientFrom(dic.Get)
	ctx := context.WithValue(context.Background(), common.CorrelationHeader, uuid.NewString()) //nolint: staticcheck
	responses, edgexErr := dc.Add(ctx, addDevicesReq)
	if edgexErr != nil {
		return edgexErr
	}

	err = nil
	for _, response := range responses {
		if response.StatusCode != http.StatusCreated {
			if response.StatusCode == http.StatusConflict {
				lc.Warnf("%s. Device may be owned by other device service instance.", response.Message)
				continue
			}

			err = multierror.Append(err, fmt.Errorf("add device failed: %s", response.Message))
		}
	}

	if err != nil {
		return errors.NewCommonEdgeXWrapper(err)
	}

	return nil
}

启动自动上报事件的定时器

对应的是AutoEventManager

s.autoEventManager.StartAutoEvents()

驱动启动

err := s.driver.Start()
if err != nil {
	cancel()
	return fmt.Errorf("failed to Start ProtocolDriver: %v", err)
}

驱动定义

上面提到了很多次的驱动,我们看一看驱动定义文件ProtocolDriver:

package interfaces

import (
	"github.com/edgexfoundry/go-mod-core-contracts/v3/models"

	sdkModels "github.com/edgexfoundry/device-sdk-go/v3/pkg/models"
)

// ProtocolDriver is a low-level device-specific interface used by
// other components of an EdgeX Device Service to interact with
// a specific class of devices.
type ProtocolDriver interface {
	// Initialize performs protocol-specific initialization for the device service.
	// The given *AsyncValues channel can be used to push asynchronous events and
	// readings to Core Data. The given []DiscoveredDevice channel is used to send
	// discovered devices that will be filtered and added to Core Metadata asynchronously.
	Initialize(sdk DeviceServiceSDK) error

	// HandleReadCommands passes a slice of CommandRequest struct each representing
	// a ResourceOperation for a specific device resource.
	HandleReadCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []sdkModels.CommandRequest) ([]*sdkModels.CommandValue, error)

	// HandleWriteCommands passes a slice of CommandRequest struct each representing
	// a ResourceOperation for a specific device resource.
	// Since the commands are actuation commands, params provide parameters for the individual
	// command.
	HandleWriteCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []sdkModels.CommandRequest, params []*sdkModels.CommandValue) error

	// Stop instructs the protocol-specific DS code to shutdown gracefully, or
	// if the force parameter is 'true', immediately. The driver is responsible
	// for closing any in-use channels, including the channel used to send async
	// readings (if supported).
	Stop(force bool) error

	// Start runs Device Service startup tasks after the SDK has been completely initialized.
	// This allows Device Service to safely use DeviceServiceSDK interface features in this function call.
	Start() error

	// AddDevice is a callback function that is invoked
	// when a new Device associated with this Device Service is added
	AddDevice(deviceName string, protocols map[string]models.ProtocolProperties, adminState models.AdminState) error

	// UpdateDevice is a callback function that is invoked
	// when a Device associated with this Device Service is updated
	UpdateDevice(deviceName string, protocols map[string]models.ProtocolProperties, adminState models.AdminState) error

	// RemoveDevice is a callback function that is invoked
	// when a Device associated with this Device Service is removed
	RemoveDevice(deviceName string, protocols map[string]models.ProtocolProperties) error

	// Discover triggers protocol specific device discovery, asynchronously
	// writes the results to the channel which is passed to the implementation
	// via ProtocolDriver.Initialize(). The results may be added to the device service
	// based on a set of acceptance criteria (i.e. Provision Watchers).
	Discover() error

	// ValidateDevice triggers device's protocol properties validation, returns error
	// if validation failed and the incoming device will not be added into EdgeX.
	ValidateDevice(device models.Device) error
}

我们再基于Device Service SDK开发对应设备驱动的时候,是需要实现这个接口的,后面会再介绍一下如果来定义一个Device Service。

golang
edgexfoundry

关于作者

justin
123456
获得点赞
文章被阅读