Skip to main content

数据接入驱动开发

本文将会详细介绍如何使用 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 项目. 使用的 JavaSpringBoot 版本见 版本说明.

info

可以使用 ideaspring initializr 功能创建项目. 也可以在 https://start.spring.iohttps://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>
info

如果 数据接入驱动 需要调用平台的接口, 可以另外引入 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": []
}
}]
}]
}

上述格式中的 devicetables.devicetables.devices.device 分别为 驱动实例配置模型配置(工作表的设备配置)资产配置(设备的设备配置).

其中 settings驱动配置信息, 与 schema 中的 settings 对应. tags数据点配置信息, 与 schema 中的 tags 对应. commands指令配置信息, 与 schema 中的 commands 对应.

info

驱动实例模型资产 中的 settings 可以不相同, 但 tagscommands 必须相同. 例如, 可以把统一的配置信息放在 驱动实例 中, 把不同的配置信息放在 模型资产 中.

配置类定义

根据前面 schema 的定义, settingstagscommands 对应的配置类的基本定义. 示例如下:


/**
* 驱动配置信息, 用来承载 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
}

info
  1. 配置类中的所有字段都必须提供 gettersetter 方法, 否则无法正常解析配置信息.
  2. 自定义数据点配置类必须继承 SDK 中的 io.github.airiot.sdk.driver.model.Tag 类, 否则会导致报错.

5. 实现驱动接口

数据接入驱动二次开发SDK中, 有两个重要接口: 数据接入驱动接口驱动与平台交互接口, 分别为 DriverAppDataSender.

  • 数据接入驱动接口(DriverApp) 该接口中定义平台控制驱动程序运行的相关方法, 是平台控制驱动程序的入口. SDK 在驱动程序启动后, 会监听平台下发的控制指令, 然后调用 数据接入驱动接口 中的方法并将执行结果返回给平台. 开发者需要实现这个接口, 并且将实现类注入到 spring 容器中. 接口定义及详细说明见 数据接入驱动接口说明.

  • 驱动与平台交互接口(DataSender) 是驱动程序与平台进行交互的接口. 驱动程序在运行过程中, 除了接收平台的控制之外, 也需要与平台进行各种交互. 例如: 向平台发送采集的到数据, 向平台发送指令执行结果、调试日志等. SDK 中定义了 驱动与平台交互接口 DataSender 以及实现类, 并且实现类已经注入到 spring 容器中. 在驱动中可直接注入 驱动与平台交互接口 即可. 接口定义及详细说明见 驱动与平台交互接口说明.

info

每个驱动程序必须向 spring 容器中注入一个 数据接入驱动接口 的实现类, 并且只能注入一个. 在程序启动时, SDK 会在 spring 容器中查找 数据接入驱动接口 的实现类. 如果没有找到或找到多个实现类时, 会抛出异常.

注: 向平台上报采集到的数据时, 必须通过 驱动与平台交互接口 中的 writePoint 方法发送, 不能直接调用 MQTT 客户端发送. 因为 SDK 会对发送的数据进行一些处理, 包括有效范围处理、数值映射、缩放比例、小数位等处理.

6. 配置驱动

这里所说 驱动配置 主要是一些静态配置信息(与 schema 中定义的配置无关), 其中包括 平台配置信息, 驱动配置信息自定义配置信息. 这些信息一般通过配置文件(application.yml)、环境变量、命令行参数等方式传入. 其中一些配置信息由平台启动驱动时通过命令行参数传入.

这些配置信息, 在开发过程中可以根据实际情况进行调整. 但是在打包时必须按照平台的要求进行配置. 打包时的配置信息见 驱动配置说明.

info

自定义配置信息 是指驱动本身的一些配置信息, 对驱动使用者不可见. 例如: 连接池大小. 这些信息一般通过配置文件(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
info

相关服务的端口号可在运维管理系统中查看. 平台中的 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
info
  1. 驱动ID驱动名称 需要在 application.yml 中手动定义.

  2. 驱动实例ID所属项目ID 在开发过程中, 需要将这些信息手动配置到 application.yml 中. 在打包时无须定义, 在平台中安装驱动时这些信息会由平台通过命令行参数传入.

7. 打包

驱动打包就是将开发完成的程序打包为可以在平台部署的驱动. 平台自身支持运行在 windowslinuxmacOS 系统中, 并且支持 x86arm 平台. 在 windows 系统中平台服务和驱动程序都是直接运行在操作系统中, 而在 linux 系统中是以 容器 的方式运行, 平台中的每个服务和驱动程序都是一个独立的容器, 所以针对不同的操作系统打包方式也不相同. 下面分别介绍在 windowslinux 系统中如何打包驱动, 对于不同平台只需要保证使用软件和库支持即可.

windows系统打包

windows 系统中, 驱动程序是直接运行在操作系统中, 所以需要将驱动程序打包为 jar 文件. 打包时需要注意的是, 不要将 application.yml 文件打包在 jar 文件内, 因为平台在安装驱动时可能会 application.yml 文件进行修改, 如果打包在 jar 内就无法修改可能会导致一些问题. 具体打包步骤如下:

  1. 打程序程序和相关资源打包为 jar 文件. 由于使用 springboot 框架开发, 所以可以直接使用 springboot 提供的插件进行打包. 在 pom.xml 中配置好相应插件后, 执行下面的命令进行打包:
mvn clean package
  1. 准备驱动配置文件 application.yml. 可以将 application.yml 放在与 jar 文件相同目录或 config 目录下, 这样驱动在启动时会自动加载该配置文件.
info

注: application.yml 中需要填写好 驱动ID驱动名称 两个配置项.

  1. 准备驱动安装配置文件 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
info

注: service.yml 中的 Name 字段的值必须与驱动配置文件中的 airiot.driver.id 的值保持一致.

  1. 将所有资源打包为 zip 文件.

jar 文件、application.ymlservice.yml 和其它资源打包为 zip 文件, 平台会根据该文件进行安装. 建议打包后的 zip 文件结构如下:

外层目录结构 配置文件目录结构

linux系统打包

由于在 linux 系统中, 驱动程序是以 容器 的方式运行, 所以打包时需要先将驱动程序打包为 docker 镜像. 然后再将镜像文件和 service.yml 打包为 .tar.gz 压缩包. 具体打包步骤如下:

  1. 打包程序程序打包为 jar 文件. 具体打包步骤参考 windows系统打包 中的第一步. 需要注意的是, 在 linux 系统打包中, 不要求将驱动配置文件 application.yml 放在在 jar 文件外面.
  1. 准备 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"]
  1. 构建 docker 镜像.

使用上一步中的 Dockerfile 文件构建 docker 镜像, 具体命令如下:

docker build -t myDriver:1.0.0 .
  1. 导出 docker 镜像并压缩.
docker save myDriver:1.0.0 | gzip > myDriver.tar.gz
  1. 准备驱动安装配置文件 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
info

service.yml 文件中定义了 PathPorts 时, 驱动部署到平台后, 会自动将驱动中的服务添加到网关代理, 驱动启动后, 就可以通过 http[s]://IP:3030/rest/Path/otherPaths 访问平台中的服务. 其中 IP 为平台的地址, Pathservice.yml 中定义的 Path 字段的值, otherPaths 为驱动中的服务路径. 例如: http://192.168.2.100:3030/rest/myDriver/users.

  1. 将所有资源打包为 gzip 文件. 将 docker镜像service.yml 文件打包为 gzip 文件. 打包命令如下:
tar cvf myDriver-linux.tar myDriver.tar service.yml
gzip myDriver-linux.tar

打包后的 gzip 文件结构如下:

目录结构

info

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. 部署

将上一步骤中得到的驱动安装包通过 运维管理系统 上传到平台, 平台会自动解析并安装驱动. 安装成功后, 就可以在项目中使用该驱动了.

安装驱动

  1. 登录 运维管理系统, 运维管理系统的默认登录地址为 http://IP:13030/, 将 IP 换成平台地址即可.
  2. 点击左侧菜单栏中的 服务管理 选项, 进入服务管理页面.
  3. 点击页面右上角的 离线上传驱动 按钮, 选择上一步中得到的 myDriver-linux.tar.gz 文件, 点击 确定 按钮, 平台会自动解析并安装驱动.

离线上传驱动

如果驱动安装失败, 可以在 运维管理系统首页 中查看详细的日志信息.

驱动安装日志

info

不同版本的平台, 离线上传驱动 按扭的位置可能不同.

使用驱动

当驱动成功安装到平台后, 就可以在项目中使用该驱动了.

具体使用方法请参考 驱动管理.

数据接入驱动接口说明

/**
* 驱动管理接口, 主要实现对驱动运行状态的管理
*
* @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 个封装类 DriverSingleConfigDriverConfig. 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 密码