数据接入驱动开发
本文将会详细介绍如何使用 Java SDK
开发自定义数据接入驱动. 示例项目目上传至 https://github.com/air-iot/sdk-java-examples/tree/master/mqtt-driver-demo.
介绍
数据接入驱动
是为实现从不同的协议、设备或其它平台系统采集数据而开发的特定程序. 每个 数据接入驱动
程序需要根据协议的特点实现数据采集功能,然后将采集到的数据通过 Java SDK
提供的接口发送到平台.
Java SDK
提供了 数据接入驱动
开发的相关内容. 包括驱动的接口定义, 以及与平台交互功能. 开发者只需要在 pom.xml
中引入 sdk-driver-starter
依赖, 定义配置信息(schema及配置类),然后实现 DriverApp
接口中的方法即可.
开发步骤
1. 创建项目
由于 Java SDK
使用了 SpringBoot
框架, 所以需要创建一个 SpringBoot
项目. 使用的 Java
和 SpringBoot
版本见 版本说明.
可以使用 idea
的 spring initializr
功能创建项目. 也可以在 https://start.spring.io 或 https://start.aliyun.com 网站上在线创建项目.
2. 引入SDK
以 maven
为例, 在 pom.xml
中引入 sdk-driver-starter
依赖.
<dependencies>
<dependency>
<groupId>io.github.air-iot</groupId>
<artifactId>sdk-driver-starter</artifactId>
<version>4.x.x</version>
</dependency>
</dependencies>
如果 数据接入驱动
需要调用平台的接口, 可以另外引入 sdk-client-http-starter
依赖.
3. 定义schema
数据接入驱动
需要定义一个 schema
用于描述驱动的配置信息. schema
是一个类似于 json
格式的对象, 详细格式说明见 数据接入驱动schema说明. 以下是一个简单的示例:
({
"driver": {
"properties": {
"settings": {
"title": "模型配置",
"type": "object",
"properties": {
"server": {
"type": "string",
"title": "MQTT Broker",
"descripption": "MQTT 服务器地址. 例如: tcp://127.0.0.1:1883"
},
"username": {
"type": "string",
"title": "用户名",
},
"password": {
"type": "string",
"title": "密码",
"fieldType": "password"
},
"network": {
"type": "object",
"title": "通讯监控参数",
"properties": {
"timeout": {
"title": "通讯超时时间(s)",
"description": "经过多长时间仪表还没有任何数据上传,认定为通讯故障",
"type": "number"
}
},
"form": [{
"key": "timeout",
}]
}
},
"required": ["server", "username", "password"]
},
"tags": {
"title": "数据点",
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"title": "Key",
"description": "数据点在 JSON 对象中的 Key. 例如: 'temperature'",
},
},
"required": ["key"]
}
},
"commands": {
"title": "命令",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "名称"
},
"ops": {
"type": "array",
"title": "指令",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "主题",
"description": "发送消息的主题. 例如: /cmd/control",
},
"message": {
"type": "string",
"title": "消息",
"description": "发送的消息. 例如: {\"cmd\":\"start\"}",
},
"qos": {
"type": "number",
"title": "QoS",
"description": "消息质量. 0,1,2",
"enum": ["0", "1", "2"],
"enum_title": ["QoS0", "QoS1", "QoS2"]
},
},
"required": ["name", "message"]
}
}
}
}
}
}
},
"model": {
"properties": {
"settings": {
"title": "模型配置",
"type": "object",
"properties": {
"topic": {
"type": "string",
"title": "主题",
"descripption": "接收数据的主题. 例如: /data/#"
},
"parseScript": {
"type": "string",
"title": "数据处理脚本",
"fieldType": "deviceScriptEdit",
"description": "消息处理脚本. 函数名必须为 'handler'",
"defaultScript": "/**\n" +
" * 数据处理脚本, 处理从 mqtt 接收到的数据.\n" +
" *\n" +
" * @param {string} topic 消息主题\n" +
" * @param {string} message 消息内容\n" +
" * @return 消息解析结果\n" +
" */\n" +
"function handler(topic, message) {\n" +
"\t\n" +
"\t// 脚本返回值必须为对象数组\n" +
"\t// \tid: 资产编号\n" +
"\t//\ttime: 时间戳(毫秒)\n" +
"\t// fields: 数据点数据. 该字段为 JSON 对象, key 为数据点标识, value 为数据点的值\n" +
"\treturn [\n" +
"\t\t{\"id\": \"SN10001\", \"time\": new Date().getTime(), \"fields\": {\"key1\": \"this is a string value\", \"key2\": true, \"key3\": 123.456}}\n" +
"\t];\n" +
"}"
},
"commandScript": {
"type": "string",
"title": "指令处理脚本",
"fieldType": "deviceScriptEdit",
"description": "指令处理脚本. 函数名必须为 'handler'",
"defaultScript": "/**\n" +
" * 指令处理脚本. 发送指令时会将指令内容传递给脚本, 然后由指定返回最终要发送的信息.\n" +
" *\n" +
" * @param {string} 工作表标识\n" +
" * @param {string} 资产编号\n" +
" * @param {object} 命令内容\n" +
" * @return {object} 最终要发送的消息, 及目标 topic\n" +
" */\n" +
"function handler(tableId, deviceId, command) {\n" +
"\t\n" +
"\t// 脚本返回值必须为下面对象结构\n" +
"\t//\t\ttopic: 消息发送的目标 topic\n" +
"\t//\t\tpayload: 消息内容\n" +
"\treturn {\n" +
"\t\t\"topic\": \"cmd/\" + deviceId,\n" +
"\t\t\"payload\": \"发送内容\"\n" +
"\t};\n" +
"}"
},
"network": {
"type": "object",
"title": "通讯监控参数",
"properties": {
"timeout": {
"title": "通讯超时时间(s)",
"description": "经过多长时间仪表还没有任何数据上传,认定为通讯故障",
"type": "number"
}
},
"form": [{
"key": "timeout",
}]
}
},
"required": ["server", "username", "password", "topic"]
},
"tags": {
"title": "数据点",
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"title": "Key",
"description": "数据点在 JSON 对象中的 Key. 例如: 'temperature'",
},
},
"required": ["key"]
}
},
"commands": {
"title": "命令",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "名称"
},
"ops": {
"type": "array",
"title": "指令",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "主题",
"description": "发送消息的主题. 例如: /cmd/control",
},
"message": {
"type": "string",
"title": "消息",
"description": "发送的消息. 例如: {\"cmd\":\"start\"}",
},
"qos": {
"type": "number",
"title": "QoS",
"description": "消息质量. 0,1,2",
"enum": ["0", "1", "2"],
"enum_title": ["QoS0", "QoS1", "QoS2"]
},
},
"required": ["name", "message"]
}
}
}
}
}
}
},
"device": {
"properties": {
"settings": {
"title": "设备配置",
"type": "object",
"properties": {
"customDeviceId": {
"type": "string",
"title": "设备编号",
"descripption": "自定义设备编号. 如果未定义则使用平台中的资产编号"
},
"network": {
"type": "object",
"title": "通讯监控参数",
"properties": {
"timeout": {
"title": "通讯超时时间(s)",
"description": "经过多长时间仪表还没有任何数据上传,认定为通讯故障",
"type": "number"
}
}
}
},
"required": []
}
}
}
})
4. 根据schema创建配置类
在上一步骤中, 通过 schema
定义了驱动的相关配置, 包括 驱动配置
、数据点配置
和 指令配置
. 接下来需要根据 schema
创建相应的配置类.
整体格式说明
驱动从平台接收到的配置信息整体格式如下:
{
"id": "mqtt-demo",
"name": "MQTT示例驱动",
"driverType": "mqtt-demo",
"device": {
"settings": {
"server": "tcp://127.0.0.1:1883",
"username": "admin",
"password": "public",
},
"tags": [
{
"id": "temperature",
"name": "温度",
"unit": "℃",
"key": "temperature"
}
],
"commands": [
{
"name": "启动",
"ops": [
{
"name": "启动",
"message": "{\"cmd\":\"start\"}",
"qos": 0
}
]
}
]
},
"tables": [{
"id": "mqtt-devices",
"device": {
"settings": {
"topic": "/data/#",
"parseScript": "/**\n * 数据处理脚本, 处理从 mqtt 接收到的数据.\n *\n * @param {string} topic 消息主题\n * @param {string} message 消息内容\n * @return 消息解析结果\n */\nfunction handler(topic, message) {\n // 发送的 topic 为 data/javasdk/javasdk001\n // 其中 javasdk001 为设备编号\n var fields = topic.split(\"/\");\n\n // 脚本返回值必须为对象数组\n // \tid: 资产编号\n //\ttime: 时间戳(毫秒)\n // fields: 数据点数据. 该字段为 JSON 对象, key 为数据点标识, value 为数据点的值\n return [\n {\"id\": fields[2], \"time\": new Date().getTime(), \"fields\": JSON.parse(message)}\n ];\n}",
"commandScript": "/**\n * 指令处理脚本. 发送指令时会将指令内容传递给脚本, 然后由指定返回最终要发送的信息.\n *\n * @param {string} 工作表标识\n * @param {string} 资产编号\n * @param {object} 命令内容\n * @return {object} 最终要发送的消息, 及目标 topic\n */\nfunction handler(tableId, deviceId, command) {\n var op = command.ops[0];\n var params = command.params;\n\n // 脚本返回值必须为下面对象结构\n //\t\ttopic: 消息发送的目标 topic\n //\t\tpayload: 消息内容\n return {\n \"topic\": op.topic + deviceId,\n \"payload\": JSON.stringify({\"cmd\": params[command.name]})\n };\n}",
},
"tags": [],
"commands": []
},
"devices": [{
"id": "sensor001",
"name": "",
"device": {
"settings": {
"customDeviceId": "sn1002003"
},
"tags": [],
"commands": []
}
}]
}]
}
上述格式中的 device
、tables.device
和 tables.devices.device
分别为 驱动实例配置
、模型配置(工作表的设备配置)
和 资产配置(设备的设备配置)
.
其中 settings
为 驱动配置信息
, 与 schema
中的 settings
对应. tags
为 数据点配置信息
, 与 schema
中的 tags
对应.
commands
为 指令配置信息
, 与 schema
中的 commands
对应.
驱动实例
、模型
和 资产
中的 settings
可以不相同, 但 tags
和 commands
必须相同.
例如, 可以把统一的配置信息放在 驱动实例
中, 把不同的配置信息放在 模型
或 资产
中.
配置类定义
根据前面 schema
的定义, settings
、tags
和 commands
对应的配置类的基本定义. 示例如下:
/**
* 驱动配置信息, 用来承载 schema 中的 settings 配置信息.
* <br>
* 该示例中, 将驱动实例、工作表和设备的驱动实例合并到一个配置类中了, 也可以分开定义.
*/
public class Settings {
private String server;
private String username;
private String password;
private String topic;
private String parseScript;
private String commandScript;
private String customDeviceId;
// get and set
}
/**
* 数据点配置信息. 除了 schema 中的 tags 配置信息外, 平台也在数据点中自定义了一些信息, 如: id、 name、 有效范围、数值转换等,
* 这部分信息会被 SDK 和平台使用, 平台自有的信息已经定义在 SDK 中的 {@link io.github.airiot.sdk.driver.model.Tag} 类中,
* 所以这里只需要定义 schema 中 tags 的配置信息然后继承平台的 Tag 类即可.
*/
public class Tag extends io.github.airiot.sdk.driver.model.Tag {
private String key;
// get and set
}
/**
* 指令信息.
*
* 其中: id, name 是平台自带信息, 必须添加到指令配置类中.
*/
public class Command {
/**
* 指令ID
*/
private String id;
/**
* 指令名称
*/
private String name;
/**
* 驱动指令配置
* <br>
* 该字段固定为数组, 但目前只有一个元素. {@code Op} 为指令的配置, 与 scheama 中的 commands 配置对应.
* 所以, 需要创建一个 Op 类用于接收指令的配置信息
*/
private List<Op> ops;
/**
* 数据写入
* <br>
* 该信息在数据点配置页面中的 '数据写入' 选项中配置, 类型固定为 Map<String, Object>, 用于接收通过 '数据写入' 方式填写的数据.
*/
private Map<String, Object> params;
// getter and setter
}
/**
* 指令的配置信息, 与 scheama 中的 commands 配置对应.
*/
public class Op {
private String topic;
private String message;
private String qos;
// getter and setter
}
/**
* 最终完整的配置类. 该类中包含了驱动配置信息、数据点配置信息和指令配置信息.
*/
public class DriverConfig {
/**
* 驱动配置信息
*/
private Settings settings;
/**
* 数据点列表
*/
private List<Tag> tags;
/**
* 指令列表
*/
private List<Command> commands;
// getter and setter
}
- 配置类中的所有字段都必须提供
getter
和setter
方法, 否则无法正常解析配置信息. - 自定义数据点配置类必须继承
SDK
中的io.github.airiot.sdk.driver.model.Tag
类, 否则会导致报错.
5. 实现驱动接口
数据接入驱动二次开发SDK中, 有两个重要接口: 数据接入驱动接口
和 驱动与平台交互接口
, 分别为 DriverApp 和 DataSender.
数据接入驱动接口(DriverApp) 该接口中定义平台控制驱动程序运行的相关方法, 是平台控制驱动程序的入口.
SDK
在驱动程序启动后, 会监听平台下发的控制指令, 然后调用数据接入驱动接口
中的方法并将执行结果返回给平台. 开发者需要实现这个接口, 并且将实现类注入到spring
容器中. 接口定义及详细说明见 数据接入驱动接口说明.驱动与平台交互接口(DataSender) 是驱动程序与平台进行交互的接口. 驱动程序在运行过程中, 除了接收平台的控制之外, 也需要与平台进行各种交互. 例如: 向平台发送采集的到数据, 向平台发送指令执行结果、调试日志等.
SDK
中定义了驱动与平台交互接口
DataSender 以及实现类, 并且实现类已经注入到spring
容器中. 在驱动中可直接注入驱动与平台交互接口
即可. 接口定义及详细说明见 驱动与平台交互接口说明.
每个驱动程序必须向 spring
容器中注入一个 数据接入驱动接口
的实现类, 并且只能注入一个. 在程序启动时, SDK
会在 spring
容器中查找 数据接入驱动接口
的实现类. 如果没有找到或找到多个实现类时, 会抛出异常.
注: 向平台上报采集到的数据时, 必须通过 驱动与平台交互接口
中的 writePoint
方法发送, 不能直接调用 MQTT
客户端发送. 因为 SDK
会对发送的数据进行一些处理, 包括有效范围处理、数值映射、缩放比例、小数位等处理.
6. 配置驱动
这里所说 驱动配置
主要是一些静态配置信息(与 schema 中定义的配置无关), 其中包括 平台配置信息
, 驱动配置信息
和 自定义配置信息
. 这些信息一般通过配置文件(application.yml)、环境变量、命令行参数等方式传入. 其中一些配置信息由平台启动驱动时通过命令行参数传入.
这些配置信息, 在开发过程中可以根据实际情况进行调整. 但是在打包时必须按照平台的要求进行配置. 打包时的配置信息见 驱动配置说明.
自定义配置信息
是指驱动本身的一些配置信息, 对驱动使用者不可见. 例如: 连接池大小. 这些信息一般通过配置文件(application.yml)、环境变量、命令行参数等方式传入.
平台配置信息
平台配置信息
主要为平台的连接信息. 包括: MQTT
连接信息, 驱动管理服务
连接信息. 内容如下:
# 驱动管理服务配置信息
driver-grpc:
# 在开发时, 需要配置为平台的地址
host: 192.168.11.101
# 端口默认为 9224, 一般无须修改. 如果有修改, 可在运维管理系统中查看 `driver` 服务的端口号
port: 9224
# 平台 MQTT 配置信息
mq:
mqtt:
# 在开发时, 需要配置为平台的地址
host: 192.168.11.101
port: 1883
username: admin
password: public
相关服务的端口号可在运维管理系统中查看.
平台中的 driver
服务的端口对应 driver-grpc.port
配置项, mqtt
服务的端口对应 mq.mqtt.port
配置项.
驱动配置信息
驱动配置信息
主要包括 驱动ID
, 驱动名称
, 驱动实例ID
, 所属项目ID
.
驱动ID
为驱动的唯一标识, 必须在平台中唯一. 需要和service.yml
文件中的Name
字段的值保持一致.驱动名称
为该驱动在平台中的显示名称.驱动实例ID
为该驱动实例的唯一标识. 同一个驱动可以创建多个实例, 每个实例的驱动ID
相同但驱动实例ID
唯一. 该信息由平台在驱动管理
中创建驱动实例时生成.所属项目ID
每个驱动实例都属于一个项目, 该驱动实例只会拿到该项目中的模型和设备信息.
airiot:
driver:
id: 驱动ID
name: 驱动名称
instance-id: 驱动实例ID
project-id: 项目ID
驱动ID
和驱动名称
需要在application.yml
中手动定义.驱动实例ID
和所属项目ID
在开发过程中, 需要将这些信息手动配置到application.yml
中. 在打包时无须定义, 在平台中安装驱动时这些信息会由平台通过命令行参数传入.
7. 打包
驱动打包就是将开发完成的程序打包为可以在平台部署的驱动. 平台自身支持运行在 windows
、linux
和 macOS
系统中, 并且支持 x86
和 arm
平台.
在 windows
系统中平台服务和驱动程序都是直接运行在操作系统中, 而在 linux
系统中是以 容器
的方式运行, 平台中的每个服务和驱动程序都是一个独立的容器, 所以针对不同的操作系统打包方式也不相同. 下面分别介绍在 windows
和 linux
系统中如何打包驱动, 对于不同平台只需要保证使用软件和库支持即可.
windows系统打包
在 windows
系统中, 驱动程序是直接运行在操作系统中, 所以需要将驱动程序打包为 jar
文件. 打包时需要注意的是, 不要将 application.yml
文件打包在 jar
文件内, 因为平台在安装驱动时可能会 application.yml
文件进行修改, 如果打包在 jar
内就无法修改可能会导致一些问题. 具体打包步骤如下:
- 打程序程序和相关资源打包为
jar
文件. 由于使用springboot
框架开发, 所以可以直接使用springboot
提供的插件进行打包. 在pom.xml
中配置好相应插件后, 执行下面的命令进行打包:
mvn clean package
- 准备驱动配置文件
application.yml
. 可以将application.yml
放在与jar
文件相同目录或config
目录下, 这样驱动在启动时会自动加载该配置文件.
注: application.yml
中需要填写好 驱动ID
和 驱动名称
两个配置项.
- 准备驱动安装配置文件
service.yml
. 在平台中安装驱动时, 需要提供一些驱动的基本信息, 例如: 版本号、驱动描述、端口号等. 这些信息需要在service.yml
中定义, 平台会根据该文件中的配置信息进行安装.service.yml
的具体格式如下:
# 必填项. 驱动名称
Name: myDriver
# 非必填项. 如果驱动对外提供 rest 服务, 则需要填写 rest 接口的统一路径前缀.
# 当填写该配置项时, 平台会自动在网关中添加该路径的路由, 并将请求转发到该驱动, 代理端口为 application.yml 文件中的 server.port 配置项.
Path: /myDriver
# 必填项. 例如: 1.0.0
Version: 1.0.0
# 非必填项.
Description: 驱动描述信息
# 驱动的配置文件名称, 平台在创建驱动时会查找驱动打包文件中查找该文件. 一般固定填写 application.yml
ConfigType: application.yml
# 必填项. 固定为 driver
GroupName: driver
# 必填项. 驱动启动命令. 还可以添加一些启动参数, 例如: -Xms512m -Xmx1024m
Command: java -jar myDriver.jar
注: service.yml
中的 Name
字段的值必须与驱动配置文件中的 airiot.driver.id
的值保持一致.
- 将所有资源打包为
zip
文件.
将 jar
文件、application.yml
、service.yml
和其它资源打包为 zip
文件, 平台会根据该文件进行安装. 建议打包后的 zip
文件结构如下:
linux系统打包
由于在 linux
系统中, 驱动程序是以 容器
的方式运行, 所以打包时需要先将驱动程序打包为 docker
镜像. 然后再将镜像文件和 service.yml
打包为 .tar.gz
压缩包. 具体打包步骤如下:
- 打包程序程序打包为
jar
文件. 具体打包步骤参考 windows系统打包 中的第一步. 需要注意的是, 在linux
系统打包中, 不要求将驱动配置文件application.yml
放在在jar
文件外面.
- 准备
Dockerfile
文件. 以下是一个简单的Dockerfile
文件示例, 具体内容根据自身的需求进行修改:
# 根据自身的需求选择合适的基础镜像
FROM openjdk:11.0.13-jre-slim-bullseye
WORKDIR /app
# 如果驱动配置文件在 jar 文件外面
COPY application.yml /app/config/
# 复制 jar 文件
COPY myDriver-*.jar /app/myDriver.jar
# 启动命令, 根据自身的需求添加启动参数
ENTRYPOINT ["java", "-Xms512m", "-Xmx512m", "-jar", "myDriver.jar"]
- 构建
docker
镜像.
使用上一步中的 Dockerfile
文件构建 docker
镜像, 具体命令如下:
docker build -t myDriver:1.0.0 .
- 导出
docker
镜像并压缩.
docker save myDriver:1.0.0 | gzip > myDriver.tar.gz
- 准备驱动安装配置文件
service.yml
. 该文件的格式与 windows系统打包 中的第三步中的service.yml
文件格式相似但又有区别. 具体格式如下:
# 必填项. 驱动名称
Name: myDriver
# 非必填项. 如果驱动对外提供 rest 服务, 则需要填写 rest 接口的统一路径前缀.
# 当填写该配置项时, 平台会自动在网关中添加该路径的路由, 并将请求转发到该驱动, 代理端口为 application.yml 文件中的 server.port 配置项.
Path: /myDriver
# 必填项. 例如: 1.0.0, 通常用镜像版本号一致
Version: 1.0.0
# 非必填项.
Description: 驱动描述信息
# 必填项. 固定为 driver
GroupName: driver
# 容器端口映射类型, 非必填项. 如果驱动需要对外提供 rest 服务, 或暴露端口时, 需要填写该配置项.
# 可选项有 None Internal External
#
# None: 不暴露端口
# Internal: 只在平台内部暴露端口. 一般为驱动对外提供 rest 服务时, 将端口映射到网关上, 填写为 Internal 即可.
# External: 对外暴露端口. 一般为驱动为作 server 端, 需要对外暴露端口以供设备连接, 此时该端口会暴露在宿主机上, 填写为 External 即可.
Service: Internal
# 非必填项. 暴露的端口列表
Ports:
- Host: "8558" # 映射到宿主机的端口号, 如果不填写, 则会随机分配一个端口号
Container: "8558" # 容器内部的端口号, 即驱动服务监听的端口号
Protocol: "" # 协议类型, 可选项有 TCP UDP, 如果不填写, 则默认为 TCP
当 service.yml
文件中定义了 Path
和 Ports
时, 驱动部署到平台后, 会自动将驱动中的服务添加到网关代理, 驱动启动后, 就可以通过 http[s]://IP:3030/rest/Path/otherPaths
访问平台中的服务. 其中 IP
为平台的地址, Path
为 service.yml
中定义的 Path
字段的值, otherPaths
为驱动中的服务路径. 例如: http://192.168.2.100:3030/rest/myDriver/users
.
- 将所有资源打包为
gzip
文件. 将docker镜像
和service.yml
文件打包为gzip
文件. 打包命令如下:
tar cvf myDriver-linux.tar myDriver.tar service.yml
gzip myDriver-linux.tar
打包后的 gzip
文件结构如下:
linux
系统整个打包过程对应的命令如下所示:
# 将驱动打包为镜像
docker build -t myDriver:1.0.0 .
# 导出镜像并压缩
docker save myDriver:1.0.0 | gzip > myDriver.tar.gz
# 将镜像文件和 service.yml 打包
tar cvf myDriver-linux.tar myDriver.tar.gz service.yml
# 对整个驱动包进行压缩
gzip myDriver-linux.tar
# 最后得到 myDriver-linux.tar.gz 压缩文件
8. 部署
将上一步骤中得到的驱动安装包通过 运维管理系统
上传到平台, 平台会自动解析并安装驱动. 安装成功后, 就可以在项目中使用该驱动了.
安装驱动
- 登录
运维管理系统
, 运维管理系统的默认登录地址为http://IP:13030/
, 将IP
换成平台地址即可. - 点击左侧菜单栏中的
服务管理
选项, 进入服务管理页面. - 点击页面右上角的
离线上传驱动
按钮, 选择上一步中得到的myDriver-linux.tar.gz
文件, 点击确定
按钮, 平台会自动解析并安装驱动.
如果驱动安装失败, 可以在 运维管理系统
的 首页
中查看详细的日志信息.
不同版本的平台, 离线上传驱动
按扭的位置可能不同.
使用驱动
当驱动成功安装到平台后, 就可以在项目中使用该驱动了.
具体使用方法请参考 驱动管理.
数据接入驱动接口说明
/**
* 驱动管理接口, 主要实现对驱动运行状态的管理
*
* @param <DriverConfig> 驱动配置信息类型, 需要与 schema 中的 {@code settings} 定义一致
* @param <Command> 指令信息类型, 需要与 schema 中的 {@code commands} 定义一致
* @param <Tag> 数据点信息类型, 需要与 schema 中的 {@code tags} 定义一致
*/
public interface DriverApp<DriverConfig, Command, Tag> {
/**
* 启动或重启驱动
* <br>
* 当驱动程序启动或与平台重新连接成功后, 会自动调用方法.
* <br>
* 注: 当驱动与平台连接断开并重连成功后也会调用该方法, 所以在实现该方法时, 需要处理好之前已经创建的资源. 例如: 已经建立的 TCP 连接等
*
* @param config 驱动实例, 模型及资产信息
*/
void start(DriverConfig config);
/**
* 停止驱动
* <br>
* 当驱动进程退出时, 会调用该方法, 可以在该法内完成对相关资产的清理工作
*/
void stop();
/**
* 执行平台对设备下发指令
* <br>
* 如果有额外需要保存的指令执行信息时, 可通过 {@link DataSender#writeRunLog(RunLog)} 方法上报到平台.
*
* @param request 指令信息
* @return 指令下发结果
*/
Object run(Cmd<Command> request);
/**
* 批量下发指令
* <br>
* 如果有额外需要保存的指令执行信息时, 可通过 {@link DataSender#writeRunLog(RunLog)} 方法上报到平台.
*
* @param request 指令信息
* @return 指令下发结果
*/
Object batchRun(BatchCmd<Command> request);
/**
* 向数据点写入数据
* <br>
* 有些驱动支持向数据点写入数据, 例如: OPC UA, 即可以从数据点读取数据, 也可以向数据点写入数据.
* <br>
* 如果驱动支持该功能, 需要在 schema 的 tags 定义中添加 rw 属性, 并且在数据点中设置为 true.
* <br>
* 详情请参考官方档中的 '数据接入驱动配置说明'
*
* @param request 指令信息
* @return 指令下发结果
*/
Object writeTag(Cmd<Tag> request);
/**
* 驱动调试
* <br>
* 该功能未实现
*/
default Debug debug(Debug config) {
return config;
}
/**
* 获取驱动配置 schema 定义
* <br>
* 通常情况下, 可以将 schema 定义写在驱动程序的 resources 目录下, 然后通过 {@link ClassLoader#getResourceAsStream(String)} 方法获取. 示例如下:
*
* <pre>
* try (InputStream stream = this.getClass().getResourceAsStream("/schema.js")) {
* if (stream == null) {
* throw new IOException("未找到驱动配置文件");
* }
*
* byte[] data = new byte[stream.available()];
* int n = stream.read(data);
* if (n != data.length) {
* throw new IllegalStateException("读取驱动配置文件异常, 数据不完整");
* }
* return new String(data, StandardCharsets.UTF_8);
* } catch (IOException e) {
* throw new IllegalStateException("读取驱动配置文件异常", e);
* }
* </pre>
*
* @return 表单 schema
*/
String schema();
}
泛型说明
DriverApp
接口要求提供 3 个泛型信息, 分别为 驱动实例配置类型
、指令配置类型
和 数据点配置类型
.
- 驱动实例配置类型
SDK
中提供了 2 个封装类 DriverSingleConfig 和 DriverConfig.DriverSingleConfig
类适用于驱动实例
、模型
和设备
的驱动配置信息一致的场景. 此时, 只需要编写一个驱动配置类即可. 然后使用DriverSingleConfig<自定义驱动配置类>
作为第 1 个泛型参数使用. 而DriverConfig
适用于驱动实例
、模型
和设备
的配置不相同的场景, 需要分别编写相应的配置类. 注:自定义驱动配置类
的定义请参考 驱动配置类型 中的DriverConfig
类. - 指令配置类型 参考 配置类定义 中的
Command
类. - 数据点配置类型 参考 配置类定义 中的
Tag
类.
DriverApp
实现类的定义示例如下:
/**
* 该示例中使用了 SDK 中提供的 DriverSingleConfig 类作为驱动实例配置类。
*
* 注: 需要将该类注入到 spring 容器中.
*/
@Component
public class MyDriver implements DriverApp<DriverSingleConfig<DriverConfig>, Command, Tag> {
// ..
}
驱动与平台交互接口说明
/**
* 驱动与平台交互接口
* <br>
* 主要用于驱动向平台上报采集到的数据, 运行过程中的重要事件, 日志等信息
*/
public interface DataSender {
/**
* 上报驱动采集到的数据
*
* @param point 数据点
* @throws IllegalStateException 如果连接未建立或已断开
* @throws DataSenderException 如果上报数据时发生异常
*/
void writePoint(Point point) throws DataSenderException;
/**
* 上报资产采集到的数据. 部分信息会自动填充
*
* @param tableId 设备所属工作表标识
* @param deviceId 设备编号
* @param time 数据产生的时间. unix时间戳(ms)
* @param tagValues 数据点的值. value 为 {@code null}的数据点不会上报
* @throws IllegalStateException 如果连接未建立或已断开
* @throws IllegalArgumentException 如果设备不存在或者数据点信息不正确
* @throws DataSenderException 如果上报数据时发生异常
*/
void writePoint(String tableId, String deviceId, long time, Map<String, Object> tagValues) throws DataSenderException;
/**
* 上报资产采集到的数据. 部分信息会自动填充. 使用服务器接收到数据的时间作为数据产生时间
*
* @param tableId 设备所属工作表标识
* @param deviceId 设备编号
* @param tagValues 数据点的值. value 为 {@code null}的数据点不会上报
* @throws IllegalStateException 如果连接未建立或已断开
* @throws IllegalArgumentException 如果设备不存在或者数据点信息不正确
* @throws DataSenderException 如果上报数据时发生异常
*/
default void writePoint(String tableId, String deviceId, Map<String, Object> tagValues) throws DataSenderException {
this.writePoint(tableId, deviceId, 0L, tagValues);
}
/**
* 发送事件
*
* @param event 事件信息
* @return 事件发送结果
* @throws IllegalStateException 如果连接未建立或已断开
* @throws EventSenderException 如果发送事件时发生异常
*/
Response writeEvent(Event event) throws EventSenderException;
/**
* 上报指令执行结果日志.
* <br>
* 一条指令可以产生多条日志
*
* @param runLog 日志
* @return 上报结果
* @throws IllegalStateException 如果连接未建立或已断开
* @throws RunLogSenderException 如果发送运行日志时发生异常
*/
Response writeRunLog(RunLog runLog) throws RunLogSenderException;
/**
* 更新设备信息
*
* @param tableDTO 设备信息
* @return 更新结果
* @throws IllegalArgumentException 如果参数不正确
* @throws UpdateTableDataException 如果接口调用失败
*/
Response updateTableData(UpdateTableDTO tableDTO) throws UpdateTableDataException;
/**
* 更新设备信息
*
* @param tableId 设备所在工作表标识
* @param rowId 设备编号
* @param fields 更新的字段信息
* @return 更新结果
*/
default Response updateTableData(String tableId, String rowId, Map<String, Object> fields) {
return this.updateTableData(new UpdateTableDTO(tableId, rowId, fields));
}
/**
* 写入 {@code debug } 级别日志
* <br>
* 该日志可以在设备调试窗口中看到
*
* @param tableId 设备所属工作表标识
* @param deviceId 设备编号
* @param msg 日志内容
* @throws IllegalStateException 如果连接未建立或已断开
* @throws LogSenderException 如果写日志时发生异常
*/
void logDebug(String tableId, String deviceId, String msg) throws LogSenderException;
/**
* 写入 {@code info } 级别日志
* <br>
* 该日志可以在设备调试窗口中看到
*
* @param tableId 设备所属工作表标识
* @param deviceId 设备编号
* @param msg 日志内容
* @throws IllegalStateException 如果连接未建立或已断开
* @throws LogSenderException 如果写日志时发生异常
*/
void logInfo(String tableId, String deviceId, String msg) throws LogSenderException;
/**
* 写入 {@code warn} 级别日志
* <br>
* 该日志可以在设备调试窗口中看到
*
* @param tableId 设备所属工作表标识
* @param deviceId 设备编号
* @param msg 日志内容
* @throws IllegalStateException 如果连接未建立或已断开
* @throws LogSenderException 如果写日志时发生异常
*/
void logWarn(String tableId, String deviceId, String msg) throws LogSenderException;
/**
* 写入 {@code error} 级别日志
* <br>
* 该日志可以在设备调试窗口中看到
*
* @param tableId 设备所属工作表标识
* @param deviceId 设备编号
* @param msg 日志内容
* @throws IllegalStateException 如果连接未建立或已断开
* @throws LogSenderException 如果写日志时发生异常
*/
void logError(String tableId, String deviceId, String msg) throws LogSenderException;
}
在 SDK
中已经有上面的接口的实现类并且已经注入到 spring
容器中, 可以直接注入. 使用方式如下所示:
/**
* 自定义驱动实现, 使用 {@link Component} 注解将该类注入到 {@code spring} 容器中.
* 并使用 {@link Autowired} 注解注入 {@link DataSender} 接口的实现类
*/
@Component
public class MyDriver implements DriverApp<DriverConfig, Command, Tag> {
/**
* 使用 {@link @Autowired} 注解注入
*/
@Autowired
private DataSender dataSender;
// 实现接口中的方法
}
驱动配置说明
以下是完整的驱动配置文件, 请参考该配置文件进行配置.
# 如果驱动提供 rest 服务时, 需要配置该项. 给所有接口添加统一前缀以方便网关进行代理
server:
port: 8080
servlet:
context-path: /driver-mydriver
# 驱动配置
airiot:
driver:
id: mydriver # 驱动ID
name: 定制开发驱动 # 驱动名称
project-id: 6438a01ec25a859defb4b98c # 项目ID, 在开发过程中需要填写, 但是发布后不需要填写
instance-id: 6238a0aec45a859defb4b6c7 # 驱动实例ID, 在开发过程中需要填写, 但是发布后不需要填写
driver-grpc:
host: 192.168.50.112 # 驱动服务(driver)地址, 在开发过程中需要填写, 但是发布后不需要填写
port: 9224 # 驱动服务(driver)端口
mq:
mqtt:
host: 192.168.50.112 # MQTT 地址
port: 1883 # MQTT 端口
username: admin # MQTT 用户名
password: public # MQTT 密码
protocol-version: 0 # MQTT 协议版本, 支持 0, 3, 4. 默认为 0, 优先使用 3.1.1 如果不支持则使用 3.1
publish-timeout: 1s # MQTT 发送消息超时时间, 默认为 1s
windows系统打包发布时的驱动配置
# 如果驱动提供 rest 服务时, 需要配置该项. 给所有接口添加统一前缀以方便网关进行代理
server:
port: 8080
servlet:
context-path: /driver-mydriver
# 驱动配置
airiot:
driver:
id: mydriver # 驱动ID
name: 定制开发驱动 # 驱动名称
driver-grpc:
host: 127.0.0.1 # 驱动服务(driver)地址, 在开发过程中需要填写, 但是发布后不需要填写
port: 9224 # 驱动服务(driver)端口
mq:
mqtt:
host: 127.0.0.1 # MQTT 地址
port: 1883 # MQTT 端口
username: admin # MQTT 用户名
password: public # MQTT 密码
linux系统打包发布时的驱动配置
# 如果驱动提供 rest 服务时, 需要配置该项. 给所有接口添加统一前缀以方便网关进行代理
server:
port: 8080
servlet:
context-path: /driver-mydriver
# 驱动配置
airiot:
driver:
id: mydriver # 驱动ID
name: 定制开发驱动 # 驱动名称
driver-grpc:
host: driver # 驱动服务(driver)地址, 在开发过程中需要填写, 但是发布后不需要填写
port: 9224 # 驱动服务(driver)端口
mq:
mqtt:
host: mqtt # MQTT 地址
port: 1883 # MQTT 端口
username: admin # MQTT 用户名
password: public # MQTT 密码