EDGEX FOUNDRY --- Device Service(设备服务)

目录

启动参数

配置文件

Writable

ServiceInfo

Registry

Clients

Device

Logging

Watchers

DeviceList

Driver

启动顺序

内部逻辑


设备服务是物理设备与EdgeX信息交换的桥梁,每个设备服务管理接口一致的同类多个设备。官方目前提供了支持mqtt,snmp,modbus等协议的设备服务,其他协议的设备服务需要另外开发。设备服务是物理设备与EdgeX通信的桥梁,每个设备服务管理接口一致的同类多个设备。

// ProtocolDriver is a low-level device-specific interface used by
// 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.
	Initialize(lc logger.LoggingClient, asyncCh chan<- *AsyncValues) error

	// HandleReadCommands passes a slice of CommandRequest struct each representing
	// a ResourceOperation for a specific device resource.
	HandleReadCommands(deviceName string, protocols map[string]contract.ProtocolProperties, reqs []CommandRequest) ([]*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]contract.ProtocolProperties, reqs []CommandRequest, params []*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
}

官方提供了device-sdk-go,开发者只需要实现ProtocolDriver接口便可开发一个针对特定协议的设备服务。本文以官方提供的device-mqtt-go分析设备服务的内部运行逻辑。


启动参数

Usage: %s [options]
Server Options:
    -r, --registry                  Indicates service should use Registry
    -p, --profile <name>            Indicate configuration profile other than default
    -c, --confDir                   Specify an alternate configuration directory
  • registry是一个bool量,True表示配置参数是从consul拉取,False表示从本地配置文件载入。
  • profile本地配置文件名(默认configuration.toml), 即便registry=True, 该参数也必须要指明一个配置文件,因为连接consul服务所需的host,port等参数必须从本地的配置文件中获取。
  • confDir是本地配置文件所在目录(默认./res),confDir/profile 组合可获得配置文件全名。

配置文件

[Writable]
LogLevel = 'INFO'

[Service]
Host = "localhost"
Port = 49990
ConnectRetries = 20
Labels = []
OpenMsg = "device simple started"
Timeout = 5000
EnableAsyncReadings = true
AsyncBufferSize = 16

[Registry]
Host = "localhost"
Port = 8500
Type = "consul"
CheckInterval = "10s"
FailLimit = 3
FailWaitTime = 10

[Clients]
  [Clients.Data]
  Name = "edgex-core-data"
  Protocol = "http"
  Host = "localhost"
  Port = 48080
  Timeout = 5000

  [Clients.Metadata]
  Name = "edgex-core-metadata"
  Protocol = "http"
  Host = "localhost"
  Port = 48081
  Timeout = 5000

  [Clients.Logging]
  Name = "edgex-support-logging"
  Protocol = "http"
  Host = "localhost"
  Port = 48061

[Device]
  DataTransform = true
  InitCmd = ""
  InitCmdArgs = ""
  MaxCmdOps = 128
  MaxCmdValueLen = 256
  RemoveCmd = ""
  RemoveCmdArgs = ""
  ProfilesDir = "./res"

[Logging]
EnableRemote = false
File = "./device-simple.log"

# Pre-define Devices
[[DeviceList]]
  Name = "Simple-Device01"
  Profile = "Simple-Device"
  Description = "Example of Simple Device"
  Labels = [ "industrial" ]
  [DeviceList.Protocols]
    [DeviceList.Protocols.other]
      Address = "simple01"
      Port = "300"
  [[DeviceList.AutoEvents]]
    Frequency = "10s"
    OnChange = false
    Resource = "Switch"
  [[DeviceList.AutoEvents]]
    Frequency = "30s"
    OnChange = false
    Resource = "Image"

如上是device-sdk-go自带的配置文件,分为7个部分,设备服务启动后会将配置文件的信息导入如下结构中,若registry=True则根据Registry中的信息连接consul并拉取相应的配置信息覆盖之前从本地配置文件中读取到的信息。

// Config is a struct which contains all of a DS's configuration settings.
type Config struct {
	// WritableInfo contains configuration settings that can be changed in the Registry .
	Writable WritableInfo
	// Service contains RegistryService-specific settings.
	Service ServiceInfo
	// Registry contains registry-specific settings.
	Registry RegistryService
	// Clients is a map of services used by a DS.
	Clients map[string]ClientInfo
	// Device contains device-specific configuration settings.
	Device DeviceInfo
	// Logging contains logging-specific configuration settings.
	Logging LoggingInfo
	// Watchers is a map provisionwatchers to be created on startup.
	Watchers map[string]WatcherInfo
	// DeviceList is the list of pre-define Devices
	DeviceList []DeviceConfig `consul:"-"`
	// Driver is a string map contains customized configuration for the protocol driver implemented based on Device SDK
	Driver map[string]string
}
  • Writable

    // WritableInfo is a struct which contains configuration settings that can be changed in the Registry .
    type WritableInfo struct {
    	// Level is the logging level of writing log message
    	LogLevel string
    }
    Writable结构中的参数可以在运行时被通过consul更新,LogLevel, 日志等级。
  • ServiceInfo

    // ServiceInfo is a struct which contains service related configuration
    // settings.
    type ServiceInfo struct {
    	// Host is the hostname or IP address of the service.
    	Host string
    	// Port is the HTTP port of the service.
    	Port int
    	// ConnectRetries is the number of times the DS will try to connect to all dependent services.
    	// If exceeded for even one dependent service, the DS will exit.
    	ConnectRetries int
    	// Labels are...
    	Labels []string
    	// OpenMsg specifies a string logged on DS startup.
    	OpenMsg string
    	// Timeout (in milliseconds) specifies both
    	// - timeout for processing REST calls and
    	// - interval time the DS will wait between each retry call.
    	Timeout int
    	// EnableAsyncReadings to determine whether the Device Service would deal with the asynchronous readings
    	EnableAsyncReadings bool
    	// AsyncBufferSize defines the size of asynchronous channel
    	AsyncBufferSize int
    }
    	Host                 提供设备服务绑定的IP,建议0.0.0.0
    	Port                 提供设备服务的端口
    	ConnectRetries       设备服务启功尝试次数上限
    	Labels               标签,便于区分不同设备服务
    	OpenMsg              启动成功消息
    	Timeout              启动失败后下一次尝试之前的等待时间
    	EnableAsyncReadings  是否支持异步读取设备数据
    	AsyncBufferSize      异步读取数据缓冲长度
  • Registry

consul连接参数

type RegistryService struct {
	// Host is the hostname or IP address of a RegistryService.
	Host string
	// Port is the HTTP port of a RegistryService.
	Port int
	// Type of Registry implementation to use, i.e. consul
	Type string
	// Timeout specifies a timeout (in milliseconds) for
	// processing REST calls from other services.
	Timeout int
	// Health check interval
	CheckInterval string
	// Maximum number of retries
	FailLimit int
	// Time to wait until next retry
	FailWaitTime int64
}
  • Clients

设备服务运行时需要使用其他微服务提供的功能,Client便是连接其他微服务所必须的参数,这是一个map参数,key值便是其他微服务的类型,value便是接入其他服务所需的具体参数信息, 设备服务正常运行一般需要使用logging, core-data, core-metaddata三种微服务。

// ClientInfo provides the host and port of another service in the eco-system.
type ClientInfo struct {
	// Name is the client service name
	Name string
	// Host is the hostname or IP address of a service.
	Host string
	// Port defines the port on which to access a given service
	Port int
	// Protocol indicates the protocol to use when accessing a given service
	Protocol string
	// Timeout specifies a timeout (in milliseconds) for
	// processing REST calls from other services.
	Timeout int
}
  • Device

    // DeviceInfo is a struct which contains device specific configuration settings.
    type DeviceInfo struct {
    	// DataTransform specifies whether or not the DS perform transformations
    	// specified by valuedescriptor on a actuation or query command.
    	DataTransform bool
    	// InitCmd specifies a device resource command which is automatically
    	// generated whenever a new device is added to the DS.
    	InitCmd string
    	// InitCmdArgs specify arguments to be used when building the InitCmd.
    	InitCmdArgs string
    	// MaxCmdOps defines the maximum number of resource operations that
    	// can be sent to a Driver in a single command.
    	MaxCmdOps int
    	// MaxCmdValueLen is the maximum string length of a command parameter or
    	// result (including the valuedescriptor name) that can be returned
    	// by a Driver.
    	MaxCmdValueLen int
    	// InitCmd specifies a device resource command which is automatically
    	// generated whenever a new device is removed from the DS.
    	RemoveCmd string
    	// RemoveCmdArgs specify arguments to be used when building the RemoveCmd.
    	RemoveCmdArgs string
    	// ProfilesDir specifies a directory which contains deviceprofile
    	// files which should be imported on startup.
    	ProfilesDir string
    }
    DataTransform 是否对读取的设备数据进行转换,例如mask,shift,base,scale,offset.
    InitCmd 新设备加入时的初始化指令
    InitCmdArgs 初始化指令参数
    MaxCmdOps 单指令中所包含的最大可擦做资源数
    MaxCmdValueLen 指令参数或相应结果的最大长度
    RemoveCmd 设备移除时的指令
    RemoveCmdArgs 设备移除指令参数
    ProfilesDir 设备profile目录,目录下所有.yml或.yaml后缀的文件将被当作deviceprofile文件
  • Logging

   EnableRemote True: log于support-logging微服务持久化(support-logging的连接参数由Clients字段提供), False:log于本地文件持久化
   File         log本地持久化时日志文件的路径

// LoggingInfo is a struct which contains logging specific configuration settings.
type LoggingInfo struct {
	// EnableRemote defines whether to use Logging Service
	EnableRemote bool
	// File is the pathname of a local log file to be created.
	File string
}

 

  • Watchers

    // WatcherInfo is a struct which contains provisionwatcher configuration settings.
    type WatcherInfo struct {
    	Profile     string
    	Key         string
    	MatchString string
    }

 

 

 

  • DeviceList

// DeviceConfig is the definition of Devices which will be auto created when the Device Service starts up
type DeviceConfig struct {
	// Name is the Device name
	Name string
	// Profile is the profile name of the Device
	Profile string

	Description string
	// Other labels applied to the device to help with searching
	Labels []string
	// Protocols for the device - stores protocol properties
	Protocols map[string]dsModels.ProtocolProperties
	// AutoEvent supports auto-generated events sourced from a device service
	AutoEvents []dsModels.AutoEvent
}

 设备模型,设备服务启动时会读取该参数并写入cache。

  • Driver

协议驱动特定的配置参数,不同协议该字段可能有不同内容。


启动顺序

创建驱动协议驱动结构体(该结构体必须实现ProtocolDriver接口),并将设备服务名,版本与该结构体传入BootStrap启动设备服务,Bootstrap继续调用startService启动服务。

func main() {
	sd := driver.NewProtocolDriver()
	startup.Bootstrap(serviceName, device_mqtt.Version, sd)
}

startService依次调用device.NewSerive创建设备服务实例,listenForInterrupt侦听中断,s.Start启动设备服务实例。若一切正常该函数永不退出,除非errChan通道收到出错信息才会继续运行s.Stop返回并停止服务。

func startService(serviceName string, serviceVersion string, driver dsModels.ProtocolDriver) error {
	s, err := device.NewService(serviceName, serviceVersion, confProfile, confDir, useRegistry, driver)
	if err != nil {
		return err
	}

	fmt.Fprintf(os.Stdout, "Calling service.Start.\n")
	errChan := make(chan error, 2)
	listenForInterrupt(errChan)
	go s.Start(errChan)

	err = <-errChan
	fmt.Fprintf(os.Stdout, "Terminating: %v.\n", err)

	return s.Stop(false)
}
func listenForInterrupt(errChan chan error) {
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errChan <- fmt.Errorf("%s", <-c)
	}()
}

device.NewService依次记录当前启动时间,服务名,服务版本,以及载入profile配置文件并将读取的配置信息写入common.CurrentConfig,最后申请Service实例并赋值启动时间和服务配置并返回。其中LoadConfig根据useRegistry会选择从consul或本地文件装在配置参数结构体。

func NewService(serviceName string, serviceVersion string, confProfile string, confDir string, useRegistry string, proto dsModels.ProtocolDriver) (*Service, error) {
	startTime := time.Now()
	if svc != nil {
		err := fmt.Errorf("NewService: service already exists!\n")
		return nil, err
	}

	if len(serviceName) == 0 {
		err := fmt.Errorf("NewService: empty name specified\n")
		return nil, err
	}
	common.ServiceName = serviceName

	config, err := configLoader.LoadConfig(useRegistry, confProfile, confDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error loading config file: %v\n", err)
		os.Exit(1)
	}
	common.CurrentConfig = config

	if len(serviceVersion) == 0 {
		err := fmt.Errorf("NewService: empty version number specified\n")
		return nil, err
	}
	common.ServiceVersion = serviceVersion

	if proto == nil {
		err := fmt.Errorf("NewService: no Driver specified\n")
		return nil, err
	}

	svc = &Service{}
	svc.startTime = startTime
	svc.svcInfo = &config.Service
	common.Driver = proto

	return svc, nil
}
errChan通道的写入源有两个,一个来自系统中断信号signal.Notify(c, syscall.SIGINT, syscall.SIGTERM),另一个来自s.Start启动失败信号。
func (s *Service) Start(errChan chan error) (err error) {
	err = clients.InitDependencyClients()
	if err != nil {
		return err
	}

	// If useRegistry selected then configLoader.RegistryClient will not be nil
	if configLoader.RegistryClient != nil {
		// Logging has now been initialized so can start listening for configuration changes.
		go configLoader.ListenForConfigChanges()
	}

	err = selfRegister()
	if err != nil {
		return fmt.Errorf("Couldn't register to metadata service")
	}

	// initialize devices, objects & profiles
	cache.InitCache()
	err = provision.LoadProfiles(common.CurrentConfig.Device.ProfilesDir)
	if err != nil {
		return fmt.Errorf("Failed to create the pre-defined Device Profiles")
	}

	err = provision.LoadDevices(common.CurrentConfig.DeviceList)
	if err != nil {
		return fmt.Errorf("Failed to create the pre-defined Devices")
	}

	s.cw = newWatchers()

	// initialize driver
	if common.CurrentConfig.Service.EnableAsyncReadings {
		s.asyncCh = make(chan *dsModels.AsyncValues, common.CurrentConfig.Service.AsyncBufferSize)
		go processAsyncResults()
	}
	err = common.Driver.Initialize(common.LoggingClient, s.asyncCh)
	if err != nil {
		return fmt.Errorf("Driver.Initialize failure: %v", err)
	}

	// Setup REST API
	r := controller.InitRestRoutes()

	autoevent.GetManager().StartAutoEvents()
	http.TimeoutHandler(nil, time.Millisecond*time.Duration(s.svcInfo.Timeout), "Request timed out")

	// TODO: call ListenAndServe in a goroutine

	common.LoggingClient.Info(fmt.Sprintf("*Service Start() called, name=%s, version=%s", common.ServiceName, common.ServiceVersion))

	go func() {
		errChan <- http.ListenAndServe(common.Colon+strconv.Itoa(s.svcInfo.Port), r)
	}()

	common.LoggingClient.Info("Listening on port: " + strconv.Itoa(common.CurrentConfig.Service.Port))
	common.LoggingClient.Info("Service started in: " + time.Since(s.startTime).String())

	common.LoggingClient.Debug("*Service Start() exit")

	return err
}

s.Start启动服务过程:

1,调用clients.InitDependencyClients初始化设备服务正常运行所必须的客户端,其内部初始化逻辑是:检查配置参数中连接core-data与core-metadata的IP和PORT的完整性;初始化loggingClient;检查core-data与core-metadata服务是否正常运行;初始化连接core-data与core-metadata的客户端。

2,如果useRegistry,开启configLoader.ListenForConfigChanges协程监测并更新配置结构中Writable。

3,selfRegister注册自己,如果consul中已经存在相同服务名的实例则直接拉区相应服务实例参数,若果不存在则创建新的服务实例并写入consul。最后设置svc.initialized = true表明初始化成功。

4,初始化cache,cache首先从consul拉取数据初始化,然后调用provision.LoadProfiles和provision.LoadDevices从配置参数中更新cache。

5,s.cw = newWatchers()

6,若使能异步读取设备数据则启动processAsyncResults协程,他直接接受来自svc.asyncCh的设备测量数据并处理打包最终发送给core-data。

7,调用common.Driver.Initialize初始化协议驱动,该函数即是设备服务开发者实现ProtocolDriver接口的4个函数之一。

8,r := controller.InitRestRoutes()初始化REST API接口,这些接口对外部提供相关服务,包括读取设备数据或向设备写指令。

9,autoevent.GetManager().StartAutoEvents()启动cache中每个设备模型中的自动测量事件。Device模型中有个AutoEvent字段描述了自动测量的频率与资源。该函数会为每一个自动测量事件创建一个协程,该协程会使用ProtocolDriver接口中的HandleReadCommands读取设备数据并将数据发送至core-data。

10,errChan <- http.ListenAndServe(common.Colon+strconv.Itoa(s.svcInfo.Port), r)启动HTTP Server,至此所有启动工作完成,设备服务开始正常工作。


内部逻辑

设备服务

设备服务启动完成后的内部逻辑大致如上图所示,cache分别从metadata和配置文件读取信息,manager为cache中每一个设备创建完成其AutoEvent的Executor,Executor使用Driver中的HandleReadCommands读取设备数据,并将该数据发送给coredata。device-mqtt-go支持异步读取设备数据,因此Driver.Initialize注册onIncomingDataReceived被动接收来自设备的数据并传入asynCh通道,processAsyncResults接收该数据并打包然后传送至coredata。http服务提供7个接口,最重要的便是command,他实现了对物理设备的数据读取与写指令功能。

<think>我们正在使用EdgeX Foundry通过Modbus-TCP协议采集电表数据。EdgeX Foundry是一个开源的物联网边缘计算平台,支持多种协议和设备服务。针对Modbus-TCP设备EdgeX提供了Modbus设备服务device-modbus)来与设备通信。 配置步骤概述: 1. 部署EdgeX Foundry和Modbus设备服务 2. 配置Modbus设备服务(连接Modbus-TCP电表) 3. 创建设备配置文件(定义电表的Modbus寄存器映射) 4. 添加设备(在EdgeX中注册电表设备) 5. 通过EdgeX API读取数据 详细步骤: 步骤1:部署EdgeX Foundry和Modbus设备服务 可以使用Docker Compose或直接运行二进制文件。这里以Docker Compose为例。 首先,下载EdgeX的docker-compose文件: ``` curl -O https://raw.githubusercontent.com/edgexfoundry/edgex-compose/ireland/docker-compose.yml ``` 然后,启动所有EdgeX服务(包括核心服务、支持服务等): ``` docker-compose up -d ``` 接着,我们需要启动Modbus设备服务。在docker-compose.yml文件中添加modbus设备服务的配置(如果尚未包含)。或者,单独运行modbus设备服务容器: ``` docker run -d --name edgex-device-modbus --network=edgex_edgex-network --hostname=edgex-device-modbus -v edgex-config:/res:ro -v edgex-config:/etc/edgex/device-modbus:ro edgexfoundry/docker-device-modbus-go:2.1.0 ``` 注意:以上版本号(2.1.0)可能需要根据实际使用的EdgeX版本进行调整。 步骤2:配置Modbus设备服务 我们需要修改Modbus设备服务的配置文件,以指定连接的电表IP和端口。配置文件通常位于`/etc/edgex/device-modbus`目录下(在容器内)。我们可以通过挂载卷的方式修改配置文件。 创建本地配置文件目录,例如`./device-modbus/res`,并在其中创建`configuration.toml`文件(或修改已有的)。 在`configuration.toml`中,我们需要配置设备服务的基本设置,以及定义Modbus连接信息。例如: ```toml [Device] DataTransform = true InitCmd = "" InitCmdArgs = "" MaxCmdOps = 128 MaxCmdValueLen = 256 RemoveCmd = "" RemoveCmdArgs = "" ProfilesDir = "./res/profiles" [Driver] LoggingLevel = "INFO" # Modbus连接配置(针对每个设备,也可以在创建设备时指定) [Driver.Client] Host = "192.168.1.100" # 电表的IP地址 Port = 502 # Modbus TCP端口,默认为502 Timeout = 5 # 超时时间(秒) IdleTimeout = 5 # 空闲超时 ``` 然后,重新启动modbus设备服务容器,并挂载配置文件目录: ``` docker run -d --name edgex-device-modbus --network=edgex_edgex-network --hostname=edgex-device-modbus -v $(pwd)/device-modbus/res:/res:ro -v $(pwd)/device-modbus/res:/etc/edgex/device-modbus:ro edgexfoundry/docker-device-modbus-go:2.1.0 ``` 步骤3:创建设备配置文件(Profile) 设备配置文件定义了设备的数据点(即Modbus寄存器)如何映射到EdgeX中的资源(Resource)。我们需要创建一个YAML文件来描述电表的寄存器。 例如,假设电表有一个保持寄存器(地址0)存储电压值,类型为UINT16。我们可以创建一个名为`ElectricMeter.yaml`的配置文件。 文件内容示例: ```yaml name: "ElectricMeterProfile" manufacturer: "MeterBrand" model: "EM123" description: "Profile for electric meter" deviceResources: - name: "Voltage" description: "Line Voltage" attributes: { primaryTable: "HOLDING_REGISTERS", startingAddress: "0", rawType: "UINT16" } properties: valueType: "UINT16" readWrite: "R" scale: "0.1" # 如果原始值需要缩放,可以设置scale和offset offset: "0.0" units: "V" # 可以继续添加其他资源,如电流、功率等 ``` 将配置文件放入Modbus设备服务可以访问的目录(例如`./device-modbus/res/profiles`),然后通过EdgeX的元数据API上传该配置文件(也可以直接放在目录中,设备服务会自动加载)。 步骤4:添加设备Device) 在EdgeX中注册电表设备,将其与设备配置文件关联,并指定连接信息(如果之前在Driver中未配置,也可以在此处覆盖)。 通过EdgeX的元数据API添加设备: ``` POST http://<edgex-core-metadata-host>:59881/api/v2/device ``` 请求体示例: ```json { "apiVersion": "v2", "device": { "name": "Meter01", "description": "Modbus TCP Electric Meter", "adminState": "UNLOCKED", "operatingState": "ENABLED", "protocols": { "modbus-tcp": { "Host": "192.168.1.100", "Port": "502", "UnitID": "1" # 如果电表有Modbus从站ID(或称单元标识符) } }, "labels": [ "electric", "modbus" ], "serviceName": "edgex-device-modbus", # 设备服务名称 "profileName": "ElectricMeterProfile", # 配置文件名称 "autoEvents": [ { "interval": "30s", # 每隔30秒自动读取一次 "onChange": false, "sourceName": "Voltage" # 要读取的资源名称 } ] } } ``` 步骤5:通过EdgeX API读取数据 设备添加成功后,设备服务会根据autoEvents的设置定期读取数据。我们也可以通过命令API手动读取: ``` GET http://<edgex-core-command-host>:59882/api/v2/device/name/Meter01/Voltage ``` 注意事项: - 确保EdgeX设备服务容器能够访问电表(网络连通,端口开放)。 - 根据电表的具体Modbus寄存器映射表来配置设备资源(地址、数据类型等)。 以上是使用EdgeX Foundry通过Modbus-TCP协议采集电表数据的基本配置方法。 §§相关问题§§ 1. EdgeX Foundry支持哪些通信协议? 2. 如何配置Modbus RTU设备? 3. EdgeX Foundry的数据如何转发到云端? 4. 如何处理Modbus协议中的不同数据类型(如浮点数、长整型)? 5. EdgeX Foundry的安全性如何保障?</think>### EdgeX Foundry Modbus-TCP电表数据采集配置方法 以下是使用EdgeX Foundry通过Modbus-TCP协议采集电表数据的完整配置流程: --- #### **1. 部署EdgeX Foundry** - 下载Docker Compose文件: ```bash curl -O https://raw.githubusercontent.com/edgexfoundry/edgex-compose/ireland/docker-compose.yml ``` - 启动EdgeX服务: ```bash docker-compose up -d ``` - 验证服务状态: ```bash docker ps -a # 检查所有容器是否正常运行 ``` --- #### **2. 配置Modbus设备服务** - **启动Modbus设备服务容器**: ```bash docker run -d --name edgex-device-modbus --network=edgex_edgex-network \ -e DEVICE_USE_MESSAGEBUS=false \ edgexfoundry/docker-device-modbus-go:2.1.0 ``` - **修改配置文件**(通过卷挂载或环境变量): ```toml [Driver] IncomingSchema = "tcp" [Driver.Client] Host = "192.168.1.100" # 电表IP地址 Port = 502 # Modbus-TCP默认端口 Timeout = "5s" # 通信超时时间 ``` --- #### **3. 创建设备配置文件(YAML格式)** 在`device-modbus/res/profiles`目录下创建电表配置文件(如`electric-meter.yaml`): ```yaml name: "ElectricMeterProfile" deviceResources: - name: "Voltage" properties: valueType: "Float32" readWrite: "R" attributes: primaryTable: "HOLDING_REGISTERS" startingAddress: "0" # 寄存器起始地址 rawType: "INT16" # 原始数据类型 scale: 0.1 # 缩放因子(实际值=原始值×0.1) - name: "Current" attributes: primaryTable: "HOLDING_REGISTERS" startingAddress: "2" rawType: "UINT32" ``` --- #### **4. 添加电表设备** 通过EdgeX元数据API注册设备: ```bash curl -X POST http://localhost:59881/api/v2/device \ -H 'Content-Type: application/json' \ -d '{ "apiVersion": "v2", "device": { "name": "Meter-01", "serviceName": "edgex-device-modbus", "profileName": "ElectricMeterProfile", "protocols": { "modbus-tcp": { "UnitID": "1", # Modbus从站地址 "PrimaryTable": "HOLDING_REGISTERS" } }, "autoEvents": [{ "interval": "30s", # 每30秒采集一次 "onChange": false, "sourceName": "Voltage" # 采集电压值 }] } }' ``` --- #### **5. 验证数据采集** - **检查设备服务日志**: ```bash docker logs edgex-device-modbus # 查看Modbus通信状态 ``` - **通过API读取数据**: ```bash curl http://localhost:59880/api/v2/reading/device/name/Meter-01 # 获取最新读数 ``` - **核心数据服务验证**: ```bash curl http://localhost:59880/api/v2/event/device/name/Meter-01 # 查看事件记录 ``` --- #### **常见问题排查** 1. **连接失败**: - 确认电表IP和端口是否正确(使用`telnet 192.168.1.100 502`测试连通性)。 - 检查防火墙是否开放502端口。 2. **数据解析错误**: - 核对寄存器地址和数据类型(参考电表Modbus协议手册)。 - 验证缩放因子(如电压值需将原始寄存器值×0.1)。 3. **服务未启动**: - 确认Docker网络是否一致(`edgex_edgex-network`)。 - 检查设备服务日志中的错误信息。 --- #### **配置示例参考** | 电表参数 | Modbus寄存器地址 | 数据类型 | 缩放因子 | EdgeX资源名 | |----------------|------------------|----------|----------|-------------| | 电压 (V) | 0x0000 | INT16 | 0.1 | Voltage | | 电流 (A) | 0x0002 | UINT32 | 0.01 | Current | | 有功功率 (kW) | 0x0005 | INT32 | 0.001 | Power | > 注:具体寄存器地址需根据电表型号的Modbus协议手册确定[^1]。 --- #### **数据流示意图** ```mermaid graph LR A[电表] -- Modbus-TCP --> B(EdgeX Modbus设备服务) B --> C[核心数据服务] C --> D[规则引擎/MQTT] D --> E[云端平台] ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值