Appearance
注: 商业版更详情教程请查看商业版文档, 购买商业版有包含对应模块的详细教程安装包
指令集
指令集是平台通过物模型自动生成的可以调用设备的精简指令, 主要用于组态和第三方平台以及联动场景中, 方便快速的控制设备。指令格式如下:
code
api->set_start_xiche&uid->1891358447719624705&address->6&value->4开放平台
用于提供给第三方平台开放接口时的认证鉴权。由于我们平台主要是一个设备的网关平台对于其他业务的功能支持并不多, 所以需要第三方业务平台来对接我们的平台从而提供具体的业务。然而为了保证数据的安全性和平台的稳定性, 我们需要对第三方平台进行http协议认证鉴权。
- 首先在开放平台页面新增一条应用记录如下:
其中, 关联账号作为此应用可以访问的权限(必填), 授权主机用于做域名校验(选填),新增的应用会自动生成appid和appsecret - aiots平台的实现如下:
java
// 第三方请求头放置三个参数
// 1. clientId 开放平台生成的appid
// 2. secret 根据timestamp+appid+appsecret进行md5加密生成的字符串
// 3. timestamp 时间戳
public class OpenApiFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 省略...
String clientId = request.getHeader("clientId");
final String secret = request.getHeader("secret");
final String timestamp = request.getHeader("timestamp");
final String secretStr = String.format("%s&%s&%s", timestamp, clientId, oauth2App.getClientSecret());
final String md5 = SecureUtil.md5(secretStr);
if(Objects.equals(secret, md5)) {
// 省略
}
// 省略...
}
}- 平台校验流程:先判断请求头是否包含指定的参数有一项不包含则进行下一步权限校验,然后判断授权主机是否符合(非必填), 最后如果参数都具备则对三个参数做md5加密在和三方平台给的加密串进行比较 , 比较通过则校验用户权限, 校验不通过直接返回校验失败
数据转发
用于将我们平台采集的数据转发给第三方平台。目前数据转发已支持mqtt客户端, 支持数据过滤, 其中数据过滤和采集入库数据过滤的配置一致
- 自定义数据转发项(比如新增kafka转发、redis转发等项)
数据源切换
数据源切换主要是指采集的数据的数据源切换, 目前支持mysql和TDengine两种数据库切换. 切换方式非常简单, 只需要在
bootstrap/pom.xml文件里面做依赖切换即可, 无需修改任何业务代码.
- 使用mysql存储采集数据(默认), 在
bootstrap/pom.xml中引入iboot-rdbms依赖:
xml
<!--多数据源(采集数据)-->
<dependency>
<groupId>com.iteaj</groupId>
<artifactId>iboot-rdbms</artifactId>
<version>${reversion}</version>
</dependency>
<!--<dependency>-->
<!-- <groupId>com.iteaj</groupId>-->
<!-- <artifactId>iboot-tdengine</artifactId>-->
<!-- <version>${reversion}</version>-->
<!--</dependency>-->- 切换到TDengine时平台将使用TDengine时序数据库存储采集数据(适合大数据量场景), 只需将
iboot-rdbms注释掉, 放开iboot-tdengine依赖即可:
xml
<!--多数据源(采集数据)-->
<!--<dependency>-->
<!-- <groupId>com.iteaj</groupId>-->
<!-- <artifactId>iboot-rdbms</artifactId>-->
<!-- <version>${reversion}</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.iteaj</groupId>
<artifactId>iboot-tdengine</artifactId>
<version>${reversion}</version>
</dependency>- 切换依赖后重新打包启动即可, 平台会自动识别引入的数据源模块并将采集数据的存储、查询等功能切换到对应的数据库
新增数据源
新增数据源包括新增数据源模块以及此模块在系统中需要实现的接口, 这些接口用来提供系统需要用到和采集的数据相关的所有功能. 如果需要自定义新增其他时序数据库(比如IoTDB、InfluxDB等)存储采集数据, 可以参照
iboot-tdengine模块的实现, 主要包含以下几个部分:
- 新建数据源模块, 在
iboot-plugin/iboot-datasource下新建Maven模块(比如iboot-iotdb、iboot-influxdb), 模块结构参照iboot-tdengine:
code
iboot-iotdb
├── src/main/java
│ ├── IotdbAutoConfiguration.java # 自动配置类, 注册入库监听器和查询服务
│ ├── listener # 数据入库(写)
│ │ ├── IotdbRealtimeData.java # 采集数据实体(对应数据库表结构)
│ │ └── IotdbRealtimeDataListener.java # 实时数据入库监听器
│ ├── mapper # 数据查询Mapper(对应xml)
│ └── service # 查询服务(读)
│ └── IotdbDataService.java # 实现RealtimeCollectDataService接口
└── src/main/resources
├── application-iotdb.yml # 数据源连接配置
├── mapper/*.xml # 查询SQL
└── META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports # 注册自动配置- 实现数据入库(写), 实现
RealtimeListenerFilter监听器接口, 实时数据会以事件的方式推送到此监听器, 在doIotEvent方法中将数据批量写入目标数据库即可. 监听器名称返回FILTER_DATABASE(数据入库), 这样会自动支持(脚本引擎/数据过滤)的入库过滤功能:
java
public class IotdbRealtimeDataListener implements RealtimeListenerFilter {
@Override
public void doIotEvent(IotEventType eventType, RealtimePayload value) {
if(isFilter(value.getType(), value)) {
// value.getPayload()为实时采集数据列表(RealtimeCollectData), 批量写入目标数据库
}
}
@Override
public String getFilterName() {
return FILTER_DATABASE; // 数据入库, 自动支持数据过滤脚本
}
@Override
public boolean isFilter(String type, RealtimePayload payload) {
// 只处理采集数据和主动上报数据
return payload.isDataFull() || payload.isDataActive();
}
}- 实现数据查询(读), 实现
RealtimeCollectDataService接口, 此接口提供系统所有和采集数据相关的查询功能, 包括: 采集数据分页查询、首页/图表统计、历史数据查询、数据指标聚合统计、关联点位查询等:
java
public class IotdbDataService implements RealtimeCollectDataService {
// 采集数据明细分页
PageResult detailOfPage(Page page, RealtimeCollectData entity);
// 按时间条件统计(图表)
List<EchartsCountValue> countOfCommonByTime(TimeCountCondition condition);
// 最近一个月/一周的采集量统计(首页)
List<BaseEchartsCount> countLastMonth();
List<BaseEchartsCount> countLastWeek(Long deviceGroupId);
// 按时间范围获取历史数据(曲线)
List<CollectValueItem> listOfCommonByTime(CollectDataCondition condition);
// 按产品模型分页查询
PageResult productOfPage(Page page, RealtimeCollectData entity);
// 点位聚合统计(最大/最小/平均值, 数据指标分析)
IndicatorStats statsOfField(String uid, Long fieldId, String start, String end);
// 关联点位查询(同设备/同采集器/同网关或分组)
List<RealtimeCollectData> associatedPoints(String uid, Long excludeFieldId, String collectTime);
List<RealtimeCollectData> listByCid(String cid, String excludeUid, Long excludeFieldId, String grade);
List<RealtimeCollectData> listByPuidOrGroup(String puid, Long deviceGroupId, Date startTime, Date endTime, String excludeUid, Long excludeFieldId, String grade);
// 省略其他接口方法, 具体参照TaosDataService实现...
}- 编写自动配置类, 使用
@ProfilesInclude注解激活模块的配置文件(如application-iotdb.yml), 并注册入库监听器和查询服务(查询服务需标注@Primary覆盖默认实现):
java
@ProfilesInclude("iotdb")
@MapperScan({"com.iteaj.iboot.plugin.datasource.iotdb.mapper"})
public class IotdbAutoConfiguration {
@Bean // 注册入库监听器
public IotdbRealtimeDataListener iotdbRealtimeDataListener() {
return new IotdbRealtimeDataListener();
}
@Bean
@Primary // 覆盖默认的采集数据查询服务
public IotdbDataService iotdbDataService() {
return new IotdbDataService();
}
}并在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中注册此自动配置类:
code
com.iteaj.iboot.plugin.datasource.iotdb.IotdbAutoConfiguration- 配置数据源连接, 在模块的
application-iotdb.yml中通过动态数据源(dynamic-datasource)配置目标数据库的连接信息, 支持按dev/test/prod多环境配置:
yaml
spring:
config:
activate:
on-profile: prod
datasource:
dynamic:
datasource:
iotdb:
username: root
password: root
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:iotdb://127.0.0.1:6667/iot
driver-class-name: org.apache.iotdb.jdbc.IoTDBDriver- 最后在
bootstrap/pom.xml中引入新模块的依赖(参考数据源切换), 重新打包启动即可, 采集数据的存储和查询功能会自动切换到新数据源
属性解析器
属性解析器支持代码解析以及脚本解析, 代码解析请看自定义解析器, 脚本解析可以在菜单(脚本引擎/属性解析)中新增脚本解析器, 然后在物模型属性配置项的属性解析器中选择脚本解析即可。新增脚本解析器如下图
, 然后在配置物模型属性的时候值解析器选择此脚本解析即可, 如图
联动脚本
联动脚本主要是以脚本的方式实现更加灵活的场景联动, 菜单在(脚本引擎/联动脚本) 脚本如图
, 此脚本由两个方法分别如下:
javascript
/**
* 指定哪些产品下的模型属性需要被触发
* 采集的数据只有匹配模型属性和状态才会执行trigger函数
* @return {productCode: [field | #status | *, ...]} 要触发的产品属性[field]以及在线状态[#status]和所有属性
*/
function matcher() {
// 1. 表示此脚本只对test_child_modbus_rtu产品的tempRtu属性值进行监控
// return {test_child_modbus_rtu: ['tempRtu']};
// 2. 表示此脚本对test_child_modbus_rtu产品的所有属性值进行监控
// return {test_child_modbus_rtu: ['*']};
// 3. 表示此脚本对test_child_modbus_rtu产品下的所有设备的在线状态变化进行监控
// return {test_child_modbus_rtu: ['#status']};
// 4. 表示此脚本对test_child_modbus_rtu产品下的所有设备的在线状态以及所有属性值进行监控
return {test_child_modbus_rtu: ['#status', '*']};
}
/**
* 数据采集或者上报以及在线状态改变的触发操作
* @param data 采集或上报的数据
* @param methods 常用方法
*/
function trigger(data, methods) {
if(methods.isRealtimeData(data)) {
// 数据采集或上报时执行这里
// 获取当前设备下的tempRtu属性值
var value = methods.getValue(uid, 'tempRtu');
// 获取当前触发设备的uid
var uid = methods.getUid(data);
} else{
// 设备在线状态改变时触发这里
var status = methods.getStatus(uid); // 获取设备的在线状态 online:在线 offline:离线
}
}调用设备指令
用来调用设备的指令, 比如控制灯的开关等
javascript
function trigger(data, methods) {
if(methods.isRealtimeData(data)) {
// 获取当前触发设备的uid
var uid = methods.getUid(data);
// 调用设备指令, 将设备的温度值改成5.5(在物模型中的指令集栏获取)
methods.invoke('api->tcmr_write_temp&dataType->float&address->0&value->5.5', uid);
// 或直接调用设备指令(此指令是平台通过物模型自动生成的可以调用设备的精简指令, 在网关子设备页面的指令集获取)
methods.invoke('api->set_start_xiche&uid->1891358447719624705&address->6&value->4');
}
}发送告警信息
用来发送告警信息, 比如发送短信、邮件等
javascript
function trigger(data, methods) {
if(methods.isRealtimeData(data)) {
// 获取当前触发设备的uid
var uid = methods.getUid(data);
// 获取当前设备下的tempRtu属性值
var value = methods.getValue(uid, 'tempRtu');
if(value > 38) {
// 发送紧急告警信息, 发送短信、邮件等, 其中1是消息模板id(告警中心/告警消息菜单), uid是设备uid, tempRtu是属性名称, value是属性值, '温度值已超过38度, 请抓紧处理'是消息内容
methods.fatalWarn(1, uid, 'tempRtu', value, '温度值已超过38度, 请抓紧处理');
// 发送解除告警消息
methods.normalWarn(1, uid, 'tempRtu', value, '温度值已恢复正常');
}
}
}值变化告警
用来实现值变化告警, 比如当温度改变时发送通知信息
javascript
function trigger(data, methods) {
if(methods.isRealtimeData(data)) {
// 获取当前触发设备的uid
var uid = methods.getUid(data);
// 获取当前设备下的tempRtu属性值
var value = methods.getValue(uid, 'tempRtu');
// 值是否有改变
if(methods.isChange(uid, 'tempRtu')) {
methods.noticeWarn(1, uid, 'tempRtu', value, '温度值已变动, 请注意处理');
}
}
}按周期告警
用来实现按周期告警, 比如第一次告警后每10分钟才发送一次(防止发送告警信息过多)
javascript
function trigger(data, methods) {
if(methods.isRealtimeData(data)) {
// 获取当前触发设备的uid
var uid = methods.getUid(data);
// 获取当前设备下的tempRtu属性值
var value = methods.getValue(uid, 'tempRtu');
// 温度大于38度并且距离上一次的告警时间过去了10分钟才发送告警信息
if(value > 38 & methods.isGtPrevWarnInterval(uid, 'tempRtu', 60 * 10)) {
methods.fatalWarn(1, uid, 'tempRtu', value, '温度值已严重超过38度, 请抓紧处理');
} else if(value <= 28) { // 温度恢复正常解除告警
methods.normalWarn(1, uid, 'tempRtu', value, '温度值已恢复正常, 解除告警');
}
}
}状态变化告警
用来实现设备在线状态变化时的告警
javascript
function trigger(data, methods) {
if(methods.isRealtimeData(data)) {
// ...
} else{
// 设备在线状态改变时触发这里
var status = methods.getStatus(uid); // 获取设备的在线状态 online:在线 offline:离线
if(status == 'offline') { // 设备离线
methods.status(1, uid, '设备离线, 请抓紧处理');
} else if(status == 'online') { // 设备上线
methods.status(1, uid, '设备上线, 恢复正常');
}
}
}数据过滤
数据过滤主要是指对采集到的数据在入库和转发的时候进行过滤, 菜单在(脚本引擎/数据过滤) 脚本如图
, 目前比较常用的为以下几种:
采集不入库
如果采集的数据不需要入库可以通过数据过滤来实现
javascript
function filter(data, methods) {
// 采集不入库
return false;
}变动值入库
用来处理采集的数据只需要对有变动的指进行入库的数据
javascript
function filter(data, methods) {
// 数据有变动才入库
return methods.isChanged(data);
}按周期入库
用来处理采集的数据按照指定的周期进行入库
javascript
function filter(data, methods) {
// 采集间隔>=60秒才入库
return methods.isGeInterval(data, 60);
}大屏和组态
大屏和组态主要是指对采集到的数据进行展示和控制, 目前使用的大屏和组态软件是乐吾乐平台的
- 平台和乐吾乐大屏进行属性绑定和初始化
属性绑定主要是指将物模型属性和乐吾乐平台的属性进行绑定, 这样在乐吾乐组态页面就可以实现对采集的数据进行实时推送和展示
- 首先导入地址和初始化代码可以从平台获取

- 2d图纸的属性导入方式: 在乐吾乐[2d]编辑器下 点击右侧栏的[通讯]栏目 -> 拉到最下方的获取变量树 -> 将平台属性导入地址复制进去

- 2d图纸的初始化方式, 在乐吾乐编辑器的右侧图纸栏拉到最下方点击[初始化JS], 然后将平台的2d初始化代码复制进去
- 大屏/组态导入方式: 在乐吾乐[大屏]编辑器的[数据]面板下 -> 新增属性 -> 在线导入(复制表格的大屏地址) -> 将平台表格的在线接口地址复制进去

- 大屏/组态图纸的初始化方式, 乐吾乐编辑器右侧画布栏拉到最下方点击[初始化动作], 然后将平台的大屏初始化代码复制进去
- 接口调用 接口调用是指在乐吾乐组态中通过调用平台的指令集接口从而控制设备的功能

, 然后在配置物模型属性的时候值解析器选择此脚本解析即可, 如图
, 此脚本由两个方法分别如下:
, 目前比较常用的为以下几种: