Compare commits

..

5 Commits

Author SHA1 Message Date
alsszer 25a8b2150b merge remote master 18 hours ago
alsszer 2402bd81df feat(plugin): 优化EMQX插件功能并增强设备管理
- 在application.yml中将运行模式从prod改为dev
- 在application-dev.yml中添加generate_statistics配置项以关闭统计信息
- 在AuthVerticle.java中添加MQTT ACL主题路径转换逻辑
- 在DeviceInfoDataCache.java和DeviceInfoDataImpl.java中增加设备状态查询方法
- 在DeviceInfoExpordVo和DeviceInfoImportVo中新增site字段用于设备位置导出导入
- 在DeviceInstallInfo.java中添加多个图片相关字段如泡沫水测漏图片、设备信息图片、门牌号图片
- 在DeviceInstallInfoDataImpl.java中实现findById方法
- 在DeviceInstallInfoExpordVo和DeviceInstallInfoVo中添加切断阀编号和报警器编号导出功能
- 在DeviceInstallInfoServiceImpl.java中完善安装信息查询逻辑
- 在DeviceManagerServiceImpl.java中支持设备状态查询参数
- 在DeviceQueryBo.java中新增deviceStatus字段用于设备状态筛选
- 在feijialuo-emqx-plugin中优化线程池配置并提升并发处理能力
- 在iot-iita-emqx-plugin中调整设备识别规则适配不同平台需求
6 months ago
小鱼干 febc6291b8 联动控制箱解析 11 months ago
小鱼干 c696dc477d 燃气报警器解析\动火离人解析 1 year ago
小鱼干 34ea26c4b6 Initial commit 1 year ago

@ -0,0 +1,13 @@
### 该问题是怎么引起的?
### 重现步骤
### 报错信息

@ -0,0 +1,53 @@
### 一、内容说明相关的Issue
### 二、建议测试周期和提测地址
建议测试完成时间xxxx.xx.xx
投产上线时间xxxx.xx.xx
提测地址CI环境/压测环境
测试账号:
### 三、变更内容
* 3.1 关联PR列表
* 3.2 数据库和部署说明
1. 常规更新
2. 重启unicorn
3. 重启sidekiq
4. 迁移任务:是否有迁移任务,没有写 "无"
5. rake脚本`bundle exec xxx RAILS_ENV = production`;没有写 "无"
* 3.4 其他技术优化内容(做了什么,变更了什么)
- 重构了 xxxx 代码
- xxxx 算法优化
* 3.5 废弃通知(什么字段、方法弃用?)
* 3.6 后向不兼容变更(是否有无法向后兼容的变更?)
### 四、研发自测点(自测哪些?冒烟用例全部自测?)
自测测试结论:
### 五、测试关注点需要提醒QA重点关注的、可能会忽略的地方
检查点:
| 需求名称 | 是否影响xx公共模块 | 是否需要xx功能 | 需求升级是否依赖其他子产品 |
|------|------------|----------|---------------|
| xxx | 否 | 需要 | 不需要 |
| | | | |
接口测试:
性能测试:
并发测试:
其他:

3
.gitignore vendored

@ -31,6 +31,3 @@ data/elasticsearch
.DS_Store
dependency-reduced-pom.xml
/data/plugins
/data/dist
/data/iot-oss

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>iot-iita-plugins</artifactId>
<groupId>cc.iotkit.plugins</groupId>
<version>2.0.19</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>DLT645-plugin</artifactId>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<plugin.build.mode>dev</plugin.build.mode>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<plugin.build.mode>prod</plugin.build.mode>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>${spring-brick.version}</version>
<configuration>
<mode>${plugin.build.mode}</mode>
<pluginInfo>
<id>DLT645-plugin</id>
<bootstrapClass>cc.iotkit.plugins.dlt645.Application</bootstrapClass>
<version>${project.version}</version>
<provider>iita</provider>
<description>DLT645示例插件</description>
<configFileName>application.yml</configFileName>
</pluginInfo>
<prodConfig>
<packageType>jar</packageType>
</prodConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,21 @@
package cc.iotkit.plugins.dlt645;
import com.gitee.starblues.bootstrap.SpringPluginBootstrap;
import com.gitee.starblues.bootstrap.annotation.OneselfConfig;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @Authortfd
* @Date2023/12/14 16:25
*/
@SpringBootApplication(scanBasePackages = "cc.iotkit.plugins.dlt645")
@OneselfConfig(mainConfigFileName = {"application.yml"})
@EnableConfigurationProperties
@EnableScheduling
public class Application extends SpringPluginBootstrap {
public static void main(String[] args) {
new Application().run(Application.class, args);
}
}

@ -0,0 +1,323 @@
package cc.iotkit.plugins.dlt645.analysis;
import cc.iotkit.plugins.dlt645.constants.DLT645Constant;
import cc.iotkit.plugins.dlt645.load.DLT645v1997CsvLoader;
import cc.iotkit.plugins.dlt645.load.DLT645v2007CsvLoader;
import cc.iotkit.plugins.dlt645.utils.ByteRef;
import cc.iotkit.plugins.dlt645.utils.BytesRef;
import cc.iotkit.plugins.dlt645.utils.ContainerUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Authortfd
* @Date2023/12/13 17:51
* DL/T 645-1997
*
* 1 6 1 1 1 N 1 1
*/
public class DLT645Analysis {
/**
*
*/
private static final DLT645Analysis DLT645Analysis = new DLT645Analysis();
public static DLT645Analysis inst() {
return DLT645Analysis;
}
/**
* :4byte[]
*/
public static final String ADR = "ADR";
/**
* 1byte
*/
public static final String FUN = "FUN";
/**
* byte[]
*/
public static final String DAT = "DAT";
/**
*
*/
private Map<String, DLT645Data> name2entity;
public static Map<String, DLT645Data> din2entity;
/**
*
*
* @param arrCmd
* @param iOffSet
* @return
*/
private static int GetVfy(byte[] arrCmd, int iOffSet) {
int iSize = arrCmd.length - 2 - iOffSet;
if (iSize < 0) {
return 0;
}
int bySum = 0x00;
int index = iOffSet;
for (int i = 0; i < iSize; i++) {
bySum += arrCmd[index++];
bySum &= 0xff;
}
return bySum & 0xff;
}
/**
*
*
* @param arrAddr 6
* @param byCmd
* @param arrData
* @return
*/
public static byte[] packCmd(byte[] arrAddr, byte byCmd, byte[] arrData) {
// 检查:数据块的大小
int iDataSize = arrData.length;
if (iDataSize > 255) {
return new byte[0];
}
if (arrAddr.length != 6) {
return new byte[0];
}
// 初始化数组大小
byte[] arrCmd = new byte[iDataSize + 13];
int index = 0;
// 前导字符(在发送帧信息之前,先发送1个或多个字节FEH,以唤醒接收方)
arrCmd[index++] = (byte) 0xFE;
// 帧起始符
arrCmd[index++] = (byte) 0x68;
// 地址码
System.arraycopy(arrAddr, 0, arrCmd, index, arrAddr.length);
index += arrAddr.length;
// 帧起始符
arrCmd[index++] = (byte) 0x68;
// 控制码
arrCmd[index++] = byCmd;
// 帧长度
arrCmd[index++] = (byte) iDataSize;
// 数据域
System.arraycopy(arrData, 0, arrCmd, index, iDataSize);
// 每个字节加上0x33
for (int i = 0; i < arrData.length; i++) {
arrCmd[index + i] = (byte) ((arrCmd[index + i] & 0xff) + 0x33);
}
index += iDataSize;
// 校验码
arrCmd[index++] = (byte) GetVfy(arrCmd, 1);
// 结束符
arrCmd[index++] = 0x16;
return arrCmd;
}
/**
*
*
* @param byCmd
* @param arrData
* @return
*/
public static byte[] packCmd(byte byCmd, byte[] arrData) {
byte[] arrAddr = new byte[6];
arrAddr[0] = 0x01;
arrAddr[1] = 0x00;
arrAddr[2] = 0x00;
arrAddr[3] = 0x00;
arrAddr[4] = 0x00;
arrAddr[5] = 0x00;
return packCmd(arrAddr, byCmd, arrData);
}
/**
*
*
* @param arrCmd
* @param arrAddrRef
* @param byCmd
* @param arrDataRef
* @return
*/
private static boolean unPackCmd2Map(byte[] arrCmd, BytesRef arrAddrRef, ByteRef byCmd, BytesRef arrDataRef) {
int iSize = arrCmd.length;
// 查找偏移量:DLT645电表前面会被塞入不定长的乱码数据被用来激活电表直到0x68字符出现
int iOffSet = 0;
int index = 0;
for (iOffSet = 0; iOffSet < iSize; iOffSet++) {
if ((arrCmd[index++] & 0xff) == 0x68) {
break;
}
}
if (iOffSet == iSize) {
return false;
}
// 检查:数据包大小
if (iSize < 12 + iOffSet) {
return false;
}
//==============================================================================
// 中国电力总局的DL/T 645-1997 多功能电能表通信规约
// 引导码 起始符 地址码 起始符 功能码 帧长度 数据域 校验和 结束符
// N 1 6 1 1 1 N 1 1
//==============================================================================
// 检查:起始符1
if (arrCmd[iOffSet + 0] != 0x68) {
return false;
}
// 检查:起始符2
if (arrCmd[iOffSet + 7] != 0x68) {
return false;
}
// 检查:结束符
if (arrCmd[iSize - 1] != 0x16) {
return false;
}
// 地址码
byte[] arrAddr = new byte[6];
System.arraycopy(arrCmd, iOffSet + 1, arrAddr, 0, 6);
arrAddrRef.setValue(arrAddr);
// 功能码
byCmd.setValue(arrCmd[iOffSet + 8]);
// 检查:帧长度
int iDataSize = arrCmd[iOffSet + 9];
if ((iDataSize + 12 + iOffSet) != iSize) {
return false;
}
// 数据域
byte[] arrData = new byte[iDataSize];
System.arraycopy(arrCmd, iOffSet + 10, arrData, 0, iDataSize);
// 每个字节先减去0x33
for (int i = 0; i < arrData.length; i++) {
arrData[i] = (byte) ((arrData[i] & 0xff) - 0x33);
}
arrDataRef.setValue(arrData);
// 检查:校验码
byte byVfyOK = (byte) (GetVfy(arrCmd, iOffSet) & 0xff);
return byVfyOK == arrCmd[iSize - 2];
}
public static boolean unPackCmd2Map(byte[] arrCmd, ByteRef byCmd, BytesRef arrData) {
BytesRef arrAddr = new BytesRef();
return unPackCmd2Map(arrCmd, arrAddr, byCmd, arrData);
}
/**
* DI0DI1
*
* @param DI0
* @param DI1
* @return
*/
public static byte[] packCmdGetData(int DI0, int DI1) {
byte[] arrData = new byte[2];
arrData[0] = (byte) DI0;
arrData[1] = (byte) DI1;
return packCmd((byte) 0x01, arrData);
}
public static boolean unPackCmdGetData(byte[] arrCmd, BytesRef arrData) {
ByteRef byCmd = new ByteRef();
if (!unPackCmd2Map(arrCmd, byCmd, arrData)) {
return false;
}
return byCmd.getValue() == 0x81;
}
/**
*
*
* @param arrCmd
* @return
*/
public static Map<String, Object> unPackCmd2Map(byte[] arrCmd) {
ByteRef byFun = new ByteRef();
BytesRef byAddr = new BytesRef();
BytesRef arrData = new BytesRef();
if (!unPackCmd2Map(arrCmd, byAddr, byFun, arrData)) {
return Collections.emptyMap();
}
Map<String, Object> value = new HashMap<>();
value.put(ADR, byAddr.getValue());
value.put(FUN, byFun.getValue());
value.put(DAT, arrData.getValue());
return value;
}
/**
*
*
* @return
*/
public synchronized Map<String, DLT645Data> getTemplateByName() {
if (this.name2entity == null) {
DLT645v1997CsvLoader loader = new DLT645v1997CsvLoader();
List<DLT645Data> entityList = loader.loadCsvFile();
Map<String, DLT645Data> nameMap = ContainerUtils.buildMapByKey(entityList, DLT645V1997Data::getName);
this.name2entity = new ConcurrentHashMap<>();
this.name2entity.putAll(nameMap);
}
return this.name2entity;
}
public synchronized Map<String, DLT645Data> getTemplateByDIn(String ver) {
if (this.din2entity == null) {
if(DLT645Constant.PRO_VER_1997.equals(ver)){
DLT645v1997CsvLoader loader = new DLT645v1997CsvLoader();
List<DLT645Data> entityList = loader.loadCsvFile();
Map<String, DLT645Data> keyMap = ContainerUtils.buildMapByKey(entityList, DLT645V1997Data::getKey);
this.din2entity = new ConcurrentHashMap<>();
this.din2entity.putAll(keyMap);
}else{
DLT645v2007CsvLoader loader = new DLT645v2007CsvLoader();
List<DLT645Data> entityList = loader.loadCsvFile();
Map<String, DLT645Data> keyMap = ContainerUtils.buildMapByKey(entityList, DLT645V2007Data::getKey);
this.din2entity = new ConcurrentHashMap<>();
this.din2entity.putAll(keyMap);
}
}
return this.din2entity;
}
}

@ -0,0 +1,44 @@
package cc.iotkit.plugins.dlt645.analysis;
import cc.iotkit.plugins.dlt645.constants.DLT645Constant;
import cc.iotkit.plugins.dlt645.utils.ByteUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* @Authortfd
* @Date2023/12/13 17:54
*/
@Slf4j
@Data
public class DLT645Converter {
public static String packData(String deviceAddress,String funCode,String dataIdentifier){
// 对设备地址进行编码
byte[] tmp = ByteUtils.hexStringToByteArray(deviceAddress);
byte[] adrr = new byte[6];
ByteUtils.byteInvertedOrder(tmp,adrr);
// 根据对象名获取对象格式信息这个格式信息记录在CSV文件中
DLT645Data dataEntity = DLT645Analysis.inst().getTemplateByDIn(DLT645Constant.PRO_VER_2007).get(dataIdentifier);
if (dataEntity == null) {
log.info("CSV模板文件中未定义对象:" + dataIdentifier + " ,你需要在模板中添加该对象信息");
}
byte byFun = Byte.decode(String.valueOf(DLT645FunCode.getCode(funCode,DLT645Constant.PRO_VER_2007)));
// 使用DLT645协议框架编码
byte[] pack = DLT645Analysis.packCmd(adrr,byFun,dataEntity.getDIn());
// 将报文按要求的16进制格式的String对象返回
return ByteUtils.byteArrayToHexString(pack,false);
}
@Data
public static class ReportData{
private String type;
private String identifier;
private Long occur;
private Long time;
private Object data;
}
}

@ -0,0 +1,92 @@
package cc.iotkit.plugins.dlt645.analysis;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* @Authortfd
* @Date2023/12/13 17:55
*/
@Slf4j
@Data
public abstract class DLT645Data {
/**
*
*/
private String name;
/**
*
*/
private DLT645DataFormat format = new DLT645DataFormat();
/**
*
*/
private int length;
/**
*
*/
private String unit;
/**
*
*/
private boolean read;
/**
*
*/
private boolean write;
/**
*
*/
private Object value = 0.0;
/**
*
*/
private Object value2nd;
public abstract String getKey();
public abstract byte[] getDIn();
public abstract void setDIn(byte[] value);
public abstract int getDInLen();
public String toString() {
if (this.value2nd == null) {
return this.name + ":" + this.value + this.unit;
}
return this.name + ":" + this.value + this.unit + " " + this.value2nd;
}
public void decodeValue(byte[] data, Map<String, DLT645Data> dinMap) {
// DI值
this.setDIn(data);
// 获取字典信息
DLT645Data dict = dinMap.get(this.getKey());
if (dict == null) {
log.info("DIn info err,please configure" + this.getKey());
}
this.format = dict.format;
this.name = dict.name;
this.read = dict.read;
this.write = dict.write;
this.length = dict.length;
this.unit = dict.unit;
// 基本值
this.value = this.format.decodeValue(data, this.format.getFormat(), this.getDInLen(), this.format.getLength());
// 组合值
if (this.format.getFormat2nd() != null && !this.format.getFormat2nd().isEmpty()) {
this.value2nd = this.format.decodeValue(data, this.format.getFormat2nd(), this.getDInLen() + this.format.getLength(), this.format.getLength2nd());
}
}
}

@ -0,0 +1,361 @@
package cc.iotkit.plugins.dlt645.analysis;
import cc.iotkit.plugins.dlt645.utils.ByteUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Authortfd
* @Date2023/12/13 17:56
*/
@Slf4j
@Data
public class DLT645DataFormat {
// 数值格式
public static final String FORMAT_X = "X";
public static final String FORMAT_N = "N";
// 时间格式
public static final String FORMAT_YYMMDDWW = "YYMMDDWW";
public static final String FORMAT_hhmmss = "hhmmss";
public static final String FORMAT_YYMMDDhhmm = "YYMMDDhhmm";
public static final String FORMAT_MMDDhhmm = "MMDDhhmm";
public static final String FORMAT_DDhh = "DDhh";
public static final String FORMAT_hhmm = "hhmm";
public static final String FORMAT_mmmm = "mmmm";
// 编码格式
public static final String FORMAT_NN___NN = "NN...NN";
public static final String FORMAT_XX___XX = "XX...XX";
// 状态格式
public static final String FORMAT_STATUS_WEEK = "周休日状态字";
public static final String FORMAT_STATUS_METER = "电表运行状态字";
public static final String FORMAT_STATUS_NETWORK = "电网状态字";
/**
*
*/
private String format = "";
/**
*
*/
private String format2nd = "";
/**
*
*/
private int length = 0;
/**
*
*/
private int length2nd = 0;
/**
*
*/
private double ratio = 1.0;
public Object decodeValue(byte[] data, String format, int start, int length) throws RuntimeException {
// 前面4个字节是DI0DI3
if (data.length < length + start) {
log.info("DATA LENGTH ERROR");
}
// 各种XX.XX格式
if (format.equals(FORMAT_X)) {
return this.getValue(data, start, length, this.ratio);
}
// 各种NN.NN格式
if (format.equals(FORMAT_N)) {
return this.getValue(data, start, length, this.ratio);
}
if (format.equals(FORMAT_NN___NN)) {
return this.getString(data, start, length);
}
// 时间格式
if (format.equals(FORMAT_hhmm) || format.equals(FORMAT_DDhh) || format.equals(FORMAT_YYMMDDWW) || format.equals(FORMAT_hhmmss) || format.equals(FORMAT_YYMMDDhhmm) || format.equals(FORMAT_MMDDhhmm)) {
return this.getDataTime(data, format, start, length);
}
if (format.equals(FORMAT_XX___XX)) {
this.format = FORMAT_XX___XX;
this.ratio = 1.0;
return true;
}
return false;
}
/**
*
* XX.XX
*
* @param format
* @param length
* @return
*/
public boolean decodeFormat(String format, int length) {
// 统计字符种类的数量
Map<Character, Integer> charCount = charCount(format);
// 组合格式XX.XXXX|YYMMDDhhmm
if (charCount.containsKey('|') && charCount.get('|').equals(1)) {
String format1 = format.substring(0, format.indexOf("|"));
String format2 = format.substring(format.indexOf("|") + 1);
this.decodeFormat(format2, -1);
this.format2nd = this.format;
this.length2nd = this.length;
this.decodeFormat(format1, -1);
return true;
}
// 各种XX.XX格式
if (charCount.containsKey('X') && charCount.containsKey('.') && charCount.get('.').equals(1)) {
this.format = FORMAT_X;
int point = format.length() - format.indexOf(".") - 1;
for (int i = 0; i < point; i++) {
this.ratio *= 10.0;
}
this.length = (format.length() - 1) / 2;
return true;
}
// XXXX格式
if (charCount.containsKey('X') && charCount.size() == 1) {
this.format = FORMAT_X;
this.ratio = 1.0;
this.length = length;
return true;
}
// 各种NN.NN格式
if (charCount.containsKey('N') && charCount.containsKey('.') && charCount.get('.').equals(1)) {
this.format = FORMAT_N;
int point = format.length() - format.indexOf(".") - 1;
for (int i = 0; i < point; i++) {
this.ratio *= 10.0;
}
this.length = (format.length() - 1) / 2;
return true;
}
// NNN格式
if (charCount.containsKey('N') && charCount.size() == 1) {
this.format = FORMAT_N;
this.ratio = 1.0;
this.length = length;
return true;
}
// 固定长度
if (this.isFixedLength(format)) {
this.format = format;
this.ratio = 1.0;
this.length = format.length() / 2;
return true;
}
// 可变长度
if (this.isVariableLength(format)) {
this.format = format;
this.ratio = 1.0;
this.length = length;
return true;
}
return false;
}
/**
*
*
* @param format
* @return
*/
private boolean isFixedLength(String format) {
if (format.equalsIgnoreCase(FORMAT_hhmm)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_DDhh)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_MMDDhhmm)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_YYMMDDhhmm)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_hhmmss)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_mmmm)) {
return true;
}
return format.equalsIgnoreCase(FORMAT_YYMMDDWW);
}
/**
* CSV
*
* @param format
* @return
*/
private boolean isVariableLength(String format) {
if (format.equalsIgnoreCase(FORMAT_NN___NN)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_XX___XX)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_STATUS_METER)) {
return true;
}
if (format.equalsIgnoreCase(FORMAT_STATUS_NETWORK)) {
return true;
}
return format.equalsIgnoreCase(FORMAT_STATUS_WEEK);
}
/**
*
*
* @param format DLT645XXX.XXX
* @return
*/
private Map<Character, Integer> charCount(String format) {
Map<Character, Integer> charSet = new HashMap<>();
for (int i = 0; i < format.length(); i++) {
Character ch = format.charAt(i);
Integer count = charSet.get(ch);
if (count == null) {
count = 0;
}
count++;
charSet.put(ch, count);
}
return charSet;
}
/**
* 4double
*
* @param data data
* @param start
* @param ratio 1000.01
* @return
*/
private Object getValue(byte[] data, int start, int length, double ratio) {
long sum = 0;
double rd = 1.0;
for (int i = 0; i < length; i++) {
long l = data[start + i] & 0x0f;
long h = (data[start + i] & 0xf0) >> 4;
l = (long) (l * rd);
sum += l;
rd = rd * 10.0;
h = (long) (h * rd);
sum += h;
rd = rd * 10.0;
}
if (ratio < 1.1 && ratio > 0.0) {
// 如果ratio==1
return sum;
} else {
return sum / ratio;
}
}
/**
*
*
* @param data data
* @param format
* @param start
* @param length
* @return
*/
private String getDataTime(byte[] data, String format, int start, int length) {
// 拆解成个位数列表
List<String> list = new ArrayList<>();
for (int i = 0; i < length; i++) {
int l = data[start + i] & 0x0f;
int h = (data[start + i] & 0xf0) >> 4;
list.add(Integer.toString(l));
list.add(Integer.toString(h));
}
// 格式1
if (format.equals(FORMAT_YYMMDDhhmm)) {
String result = "20" + list.get(9) + list.get(8) + "年" + list.get(7) + list.get(6) + "月" + list.get(5) + list.get(4) + "日";
result += " " + list.get(3) + list.get(2) + "点" + list.get(1) + list.get(0) + "分";
return result;
}
if (format.equals(FORMAT_YYMMDDWW)) {
String result = "20" + list.get(7) + list.get(6) + "年" + list.get(5) + list.get(4) + "月" + list.get(3) + list.get(2) + "日";
result += " 星期:" + list.get(1) + list.get(0);
return result;
}
if (format.equals(FORMAT_hhmmss)) {
return list.get(5) + list.get(4) + "点" + list.get(3) + list.get(2) + "分" + list.get(1) + list.get(0) + "秒";
}
if (format.equals(FORMAT_mmmm)) {
return list.get(3) + list.get(2) + list.get(1) + list.get(0) + "分";
}
if (format.equals(FORMAT_MMDDhhmm)) {
String result = list.get(7) + list.get(6) + "月" + list.get(5) + list.get(4) + "日 ";
result += list.get(3) + list.get(2) + "点" + list.get(1) + list.get(0) + "分";
return result;
}
if (format.equals(FORMAT_DDhh)) {
return list.get(3) + list.get(2) + "号 " + list.get(1) + list.get(0) + "点";
}
if (format.equals(FORMAT_hhmm)) {
return list.get(3) + list.get(2) + "点 " + list.get(1) + list.get(0) + "分";
}
return "";
}
private byte encodeBCD(byte a) {
return (byte) ((a / 10) * 16 + (a % 10));
}
private byte decodeBCD(byte a) {
return (byte) ((a / 16) * 10 + (a % 16));
}
private Object getString(byte[] data, int start, int length) {
byte[] tmp = new byte[length];
for (int i = 0; i < length; i++) {
tmp[i] = data[start + i];
}
for (int i = 0; i < length / 2; i++) {
byte by = tmp[i];
tmp[i] = tmp[length - i - 1];
tmp[length - i - 1] = by;
}
return ByteUtils.byteArrayToHexString(tmp, true).replace(" ", "");
}
}

@ -0,0 +1,302 @@
package cc.iotkit.plugins.dlt645.analysis;
import cc.iotkit.plugins.dlt645.constants.DLT645Constant;
import lombok.Data;
/**
* @Authortfd
* @Date2023/12/13 17:58
*/
@Data
public class DLT645FunCode {
/* 2007 */
private static final String func_v07_00000 = "保留";
private static final String func_v07_01000 = "广播校时";
private static final String func_v07_10001 = "读数据";
private static final String func_v07_10010 = "读后续数据";
private static final String func_v07_10011 = "读通信地址";
private static final String func_v07_10100 = "写数据";
private static final String func_v07_10101 = "写通信地址";
private static final String func_v07_10110 = "冻结";
private static final String func_v07_10111 = "更改通信速率";
private static final String func_v07_11000 = "修改密码";
private static final String func_v07_11001 = "最大需量清零";
private static final String func_v07_11010 = "电表清零";
private static final String func_v07_11011 = "事件清零";
/* 1997 */
public static final String func_v97_00000 = "保留";
public static final String func_v97_00001 = "读数据";
public static final String func_v97_00010 = "读后续数据";
public static final String func_v97_00011 = "重读数据";
public static final String func_v97_00100 = "写数据";
public static final String func_v97_01000 = "广播校时";
public static final String func_v97_01010 = "写设备地址";
public static final String func_v97_01100 = "更改通信速率";
public static final String func_v97_01111 = "修改密码";
public static final String func_v97_10000 = "最大需量清零";
/**
* =false=true
*/
private boolean direct = false;
/**
*
*/
private boolean error = false;
/**
*
*/
private byte code = 0;
/**
*
*/
private boolean next = false;
public static DLT645FunCode decodeEntity(byte func) {
DLT645FunCode dlt645FunCode = new DLT645FunCode();
dlt645FunCode.decode(func);
return dlt645FunCode;
}
public static int getCodev2007(String text) {
if (func_v07_00000.equals(text)) {
return 0b00000;
}
if (func_v07_01000.equals(text)) {
return 0b01000;
}
if (func_v07_10001.equals(text)) {
return 0b10001;
}
if (func_v07_10010.equals(text)) {
return 0b10010;
}
if (func_v07_10011.equals(text)) {
return 0b10011;
}
if (func_v07_10100.equals(text)) {
return 0b10100;
}
if (func_v07_10101.equals(text)) {
return 0b10101;
}
if (func_v07_10110.equals(text)) {
return 0b10110;
}
if (func_v07_10111.equals(text)) {
return 0b10111;
}
if (func_v07_11000.equals(text)) {
return 0b11000;
}
if (func_v07_11001.equals(text)) {
return 0b11001;
}
if (func_v07_11010.equals(text)) {
return 0b11010;
}
if (func_v07_11011.equals(text)) {
return 0b11011;
}
return 0b00000;
}
public static int getCodev1997(String text) {
if (func_v97_00000.equals(text)) {//
return 0b00000;
}
if (func_v97_00001.equals(text)) {
return 0b00001;
}
if (func_v97_00010.equals(text)) {
return 0b00010;
}
if (func_v97_00011.equals(text)) {
return 0b00011;
}
if (func_v97_00100.equals(text)) {
return 0b00100;
}
if (func_v97_01000.equals(text)) {
return 0b01000;
}
if (func_v97_01010.equals(text)) {
return 0b01010;
}
if (func_v97_01100.equals(text)) {
return 0b01100;
}
if (func_v97_01111.equals(text)) {
return 0b01111;
}
if (func_v97_10000.equals(text)) {
return 0b10000;
}
return 0b00000;
}
/**
*
*
* @return
*/
public byte encode() {
int func = 0;
if (this.direct) {
func |= 0x80;
}
if (this.error) {
func |= 0x40;
}
if (this.next) {
func |= 0x20;
}
func |= this.code & 0x1F;
return (byte) func;
}
/**
*
*
* @param dlt645FunCode
* @return
*/
public byte encodeFunCode(DLT645FunCode dlt645FunCode) {
return dlt645FunCode.encode();
}
/**
*
*
* @param func
*/
public void decode(byte func) {
this.direct = (func & 0x80) > 0;
this.error = (func & 0x40) > 0;
this.next = (func & 0x20) > 0;
this.code = (byte) (func & 0x1F);
}
public static int getCode(String text,String ver){
if(DLT645Constant.PRO_VER_1997.equals(ver)){
return getCodev1997(text);
}else{
return getCodev2007(text);
}
}
public String getCodeTextV1997() {
if (this.code == 0b00000) {
return func_v97_00000;
}
if (this.code == 0b01000) {
return func_v97_01000;
}
if (this.code == 0b00001) {
return func_v97_00001;
}
if (this.code == 0b00010) {
return func_v97_00010;
}
if (this.code == 0b00100) {
return func_v97_00100;
}
if (this.code == 0b01010) {
return func_v97_01010;
}
if (this.code == 0b01100) {
return func_v97_01100;
}
if (this.code == 0b01111) {
return func_v97_01111;
}
if (this.code == 0b10000) {
return func_v97_10000;
}
return "";
}
public String getCodeTextV2007() {
if (this.code == 0b00000) {
return func_v07_00000;
}
if (this.code == 0b01000) {
return func_v07_01000;
}
if (this.code == 0b10001) {
return func_v07_10001;
}
if (this.code == 0b10010) {
return func_v07_10010;
}
if (this.code == 0b10011) {
return func_v07_10011;
}
if (this.code == 0b10100) {
return func_v07_10100;
}
if (this.code == 0b10101) {
return func_v07_10101;
}
if (this.code == 0b10110) {
return func_v07_10110;
}
if (this.code == 0b10111) {
return func_v07_10111;
}
if (this.code == 0b11000) {
return func_v07_11000;
}
if (this.code == 0b11001) {
return func_v07_11001;
}
if (this.code == 0b11010) {
return func_v07_11010;
}
if (this.code == 0b11011) {
return func_v07_11011;
}
return "";
}
/**
*
*
* @return
*/
public String getMessage(String ver) {
String message = "";
if (this.direct) {
message += "从站发出:";
} else {
message += "主站发出:";
}
if (ver.equalsIgnoreCase(DLT645Constant.PRO_VER_1997)) {
message += this.getCodeTextV1997();
}
if (ver.equalsIgnoreCase(DLT645Constant.PRO_VER_2007)) {
message += this.getCodeTextV2007();
}
message += this.getCodeTextV1997();
if (this.error) {
message += ":异常";
} else {
message += ":正常";
}
if (this.next) {
message += ":还有后续帧";
} else {
message += ":这是末尾帧";
}
return message;
}
}

@ -0,0 +1,63 @@
package cc.iotkit.plugins.dlt645.analysis;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
* @Authortfd
* @Date2023/12/13 17:58
*/
@Slf4j
@Setter
@Getter
public class DLT645V1997Data extends DLT645Data {
/**
* DI1/DI0
*/
private byte di0l = 0;
private byte di0h = 0;
private byte di1l = 0;
private byte di1h = 0;
@Override
public String getKey() {
String key = "";
key += Integer.toString(this.di1h, 16);
key += Integer.toString(this.di1l, 16);
key += Integer.toString(this.di0h, 16);
key += Integer.toString(this.di0l, 16);
return key.toUpperCase();
}
@Override
public byte[] getDIn() {
byte[] value = new byte[2];
value[0] = (byte) (this.di0l + (this.di0h << 4));
value[1] = (byte) (this.di1l + (this.di1h << 4));
return value;
}
@Override
public void setDIn(byte[] value) {
if (value.length < 2) {
log.info("DATA LENGTH ERROR");
}
// DI值
this.di1h = (byte) ((value[1] & 0xf0) >> 4);
this.di1l = (byte) (value[1] & 0x0f);
this.di0h = (byte) ((value[0] & 0xf0) >> 4);
this.di0l = (byte) (value[0] & 0x0f);
}
/**
* 1997DIn2
*
* @return
*/
@Override
public int getDInLen() {
return 2;
}
}

@ -0,0 +1,66 @@
package cc.iotkit.plugins.dlt645.analysis;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* @Authortfd
* @Date2023/12/13 17:58
*/
@Slf4j
@Setter
@Getter
public class DLT645V2007Data extends DLT645Data {
/**
* DI1/DI0
*/
private byte di0 = 0;
private byte di1 = 0;
private byte di2 = 0;
private byte di3 = 0;
public String getKey() {
String key = "";
key += StringUtils.leftPad(Integer.toHexString(this.di3 & 0xFF),2,"0");
key += StringUtils.leftPad(Integer.toHexString(this.di2 & 0xFF),2,"0");
key += StringUtils.leftPad(Integer.toHexString(this.di1 & 0xFF),2,"0");
key += StringUtils.leftPad(Integer.toHexString(this.di0 & 0xFF),2,"0") + "";
return key.toUpperCase();
}
@Override
public byte[] getDIn() {
byte[] value = new byte[4];
value[0] = this.di0;
value[1] = this.di1;
value[2] = this.di2;
value[3] = this.di3;
return value;
}
@Override
public void setDIn(byte[] value) {
if (value.length < 4) {
log.info("DATA LENGTH ERROR");
}
this.di0 = value[0];
this.di1 = value[1];
this.di2 = value[2];
this.di3 = value[3];
}
/**
* 2007DIn 4
*
* @return
*/
@Override
public int getDInLen() {
return 4;
}
}

@ -0,0 +1,39 @@
package cc.iotkit.plugins.dlt645.conf;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.IPluginScript;
import cc.iotkit.plugin.core.LocalPluginConfig;
import cc.iotkit.plugin.core.LocalPluginScript;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugins.dlt645.service.FakeThingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @Authortfd
* @Date2023/12/13 16:54
*/
@Slf4j
@Component
public class BeanConfig {
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IThingService getThingService() {
return new FakeThingService();
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginScript getPluginScript() {
log.info("init LocalPluginScript");
return new LocalPluginScript("script.js");
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginConfig getPluginConfig() {
return new LocalPluginConfig();
}
}

@ -0,0 +1,21 @@
package cc.iotkit.plugins.dlt645.conf;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Authortfd
* @Date2023/12/13 17:01
*/
@Data
@Component
@ConfigurationProperties(prefix = "tcp")
public class TcpClientConfig {
private String host;
private int port;
private int interval;
}

@ -0,0 +1,10 @@
package cc.iotkit.plugins.dlt645.constants;
/**
* @Authortfd
* @Date2023/12/22 10:54
*/
public class DLT645Constant {
public static final String PRO_VER_1997 = "v1997";
public static final String PRO_VER_2007 = "v2007";
}

@ -0,0 +1,93 @@
package cc.iotkit.plugins.dlt645.load;
import cc.iotkit.plugins.dlt645.analysis.DLT645Data;
import cc.iotkit.plugins.dlt645.analysis.DLT645DataFormat;
import cc.iotkit.plugins.dlt645.analysis.DLT645V1997Data;
import cn.hutool.core.text.csv.CsvReader;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.util.CharsetUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* @Authortfd
* @Date2023/12/13 17:59
*/
@Slf4j
public class DLT645v1997CsvLoader {
/**
* CSV
*
*/
public List<DLT645Data> loadCsvFile() {
CsvReader csvReader = CsvUtil.getReader();
InputStreamReader dataReader=new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("DLT645-1997.csv"),CharsetUtil.CHARSET_GBK);
List<JDecoderValueParam> rows = csvReader.read(dataReader, JDecoderValueParam.class);
List<DLT645Data> list = new ArrayList<>();
for (JDecoderValueParam jDecoderValueParam : rows) {
try {
DLT645V1997Data entity = new DLT645V1997Data();
entity.setName(jDecoderValueParam.getName());
entity.setDi1h((byte) Integer.parseInt(jDecoderValueParam.di1h, 16));
entity.setDi1l((byte) Integer.parseInt(jDecoderValueParam.di1l, 16));
entity.setDi0h((byte) Integer.parseInt(jDecoderValueParam.di0h, 16));
entity.setDi0l((byte) Integer.parseInt(jDecoderValueParam.di0l, 16));
entity.setLength(jDecoderValueParam.length);
entity.setUnit(jDecoderValueParam.unit);
entity.setRead(Boolean.parseBoolean(jDecoderValueParam.read));
entity.setWrite(Boolean.parseBoolean(jDecoderValueParam.write));
DLT645DataFormat format = new DLT645DataFormat();
if (format.decodeFormat(jDecoderValueParam.format, jDecoderValueParam.length)) {
entity.setFormat(format);
} else {
log.info("DLT645 CSV记录的格式错误:" + jDecoderValueParam.getName() + ":" + jDecoderValueParam.getFormat());
continue;
}
list.add(entity);
} catch (Exception e) {
e.printStackTrace();
}
}
return list;
}
@Data
public static class JDecoderValueParam implements Serializable {
private String di1h;
private String di1l;
private String di0h;
private String di0l;
/**
*
*/
private String format;
/**
*
*/
private Integer length;
/**
*
*/
private String unit;
/**
*
*/
private String read;
/**
*
*/
private String write;
/**
*
*/
private String name;
}
}

@ -0,0 +1,95 @@
package cc.iotkit.plugins.dlt645.load;
import cc.iotkit.plugins.dlt645.analysis.DLT645Data;
import cc.iotkit.plugins.dlt645.analysis.DLT645DataFormat;
import cc.iotkit.plugins.dlt645.analysis.DLT645V2007Data;
import cn.hutool.core.text.csv.CsvReader;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.util.CharsetUtil;
import io.vertx.core.AbstractVerticle;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* @Authortfd
* @Date2023/12/13 17:59
*/
@Slf4j
public class DLT645v2007CsvLoader extends AbstractVerticle {
/**
* CSV
*
*/
public List<DLT645Data> loadCsvFile() {
CsvReader csvReader = CsvUtil.getReader();
InputStreamReader dataReader=new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("DLT645-2007.csv"),CharsetUtil.CHARSET_GBK);
List<JDecoderValueParam> rows = csvReader.read(dataReader, JDecoderValueParam.class);
List<DLT645Data> list = new ArrayList<>();
for (JDecoderValueParam jDecoderValueParam : rows) {
try {
DLT645V2007Data entity = new DLT645V2007Data();
entity.setName(jDecoderValueParam.getName());
entity.setDi0((byte) Integer.parseInt(jDecoderValueParam.di0, 16));
entity.setDi1((byte) Integer.parseInt(jDecoderValueParam.di1, 16));
entity.setDi2((byte) Integer.parseInt(jDecoderValueParam.di2, 16));
entity.setDi3((byte) Integer.parseInt(jDecoderValueParam.di3, 16));
entity.setLength(jDecoderValueParam.length);
entity.setUnit(jDecoderValueParam.unit);
entity.setRead(Boolean.parseBoolean(jDecoderValueParam.read));
entity.setWrite(Boolean.parseBoolean(jDecoderValueParam.write));
DLT645DataFormat format = new DLT645DataFormat();
if (format.decodeFormat(jDecoderValueParam.format, jDecoderValueParam.length)) {
entity.setFormat(format);
} else {
log.info("DLT645 CSV记录的格式错误:" + jDecoderValueParam.getName() + ":" + jDecoderValueParam.getFormat());
continue;
}
list.add(entity);
} catch (Exception e) {
e.printStackTrace();
}
}
return list;
}
@Data
public static class JDecoderValueParam implements Serializable {
private String di0;
private String di1;
private String di2;
private String di3;
/**
*
*/
private String format;
/**
*
*/
private Integer length;
/**
*
*/
private String unit;
/**
*
*/
private String read;
/**
*
*/
private String write;
/**
*
*/
private String name;
}
}

@ -0,0 +1,54 @@
package cc.iotkit.plugins.dlt645.service;
import cc.iotkit.plugin.core.thing.IDevice;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.down.DeviceConfig;
import cc.iotkit.plugin.core.thing.actions.down.PropertyGet;
import cc.iotkit.plugin.core.thing.actions.down.PropertySet;
import cc.iotkit.plugin.core.thing.actions.down.ServiceInvoke;
import cc.iotkit.plugins.dlt645.analysis.DLT645Converter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
/**
* @Authortfd
* @Date2023/12/14 16:22
*/
@Service
public class DLT645Device implements IDevice {
@Autowired
private TcpClientVerticle dlt645Verticle;
@Override
public ActionResult config(DeviceConfig action) {
return ActionResult.builder().code(0).reason("").build();
}
public static void main(String[] args) {
String topic = "/JN10202310300068/s/event/property/post";
String s = Arrays.asList(topic.split("/")).get(1);
topic = "/sys/*/"+s+"/c/event/property/post";
System.out.println(topic);
}
@Override
public ActionResult propertyGet(PropertyGet action) {
for (String key:action.getKeys()){
String msg=DLT645Converter.packData(action.getDeviceName(),"读数据",key.replaceFirst("p",""));
dlt645Verticle.sendMsg(msg);
}
return ActionResult.builder().code(0).reason("success").build();
}
@Override
public ActionResult propertySet(PropertySet action) {
return null;
}
@Override
public ActionResult serviceInvoke(ServiceInvoke action) {
return null;
}
}

@ -0,0 +1,88 @@
package cc.iotkit.plugins.dlt645.service;
import cc.iotkit.common.utils.JsonUtils;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugins.dlt645.conf.TcpClientConfig;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.bootstrap.realize.PluginCloseListener;
import com.gitee.starblues.core.PluginCloseType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @Authortfd
* @Date2023/12/13 16:58
*/
@Slf4j
@Service
public class Dlt645Plugin implements PluginCloseListener {
@Autowired
private PluginInfo pluginInfo;
@Autowired
private TcpClientVerticle tcpClientVerticle;
@Autowired
private TcpClientConfig tcpConfig;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IPluginConfig pluginConfig;
private Vertx vertx;
private String deployedId;
@PostConstruct
public void init() {
vertx = Vertx.vertx();
try {
//获取插件最新配置替换当前配置
Map<String, Object> config = pluginConfig.getConfig(pluginInfo.getPluginId());
BeanUtil.copyProperties(config, tcpConfig, CopyOptions.create().ignoreNullValue());
tcpClientVerticle.setConfig(tcpConfig);
Future<String> future = vertx.deployVerticle(tcpClientVerticle);
future.onSuccess((s -> {
deployedId = s;
log.info("dlt645 client plugin started success,config:"+ JsonUtils.toJsonString(tcpConfig));
}));
future.onFailure(Throwable::printStackTrace);
} catch (Throwable e) {
log.error("init dlt645 client plugin error", e);
}
}
@SneakyThrows
@Override
public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) {
log.info("plugin close,type:{},pluginId:{}", closeType, pluginInfo.getPluginId());
if (deployedId != null) {
CountDownLatch wait = new CountDownLatch(1);
Future<Void> future = vertx.undeploy(deployedId);
future.onSuccess(unused -> {
log.info("dlt645 client plugin stopped success");
wait.countDown();
});
future.onFailure(h -> {
log.info("dlt645 client plugin stopped failed");
h.printStackTrace();
wait.countDown();
});
wait.await(5, TimeUnit.SECONDS);
}
}
}

@ -0,0 +1,46 @@
package cc.iotkit.plugins.dlt645.service;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
/**
* @Authortfd
* @Date2023/12/13 16:56
*/
@Slf4j
public class FakeThingService implements IThingService {
@Override
public ActionResult post(String pluginId, IDeviceAction action) {
log.info("post action:{}", action);
return ActionResult.builder().code(0).build();
}
@Override
public ThingProduct getProduct(String pk) {
return ThingProduct.builder()
.productKey("PjmkANSTDt85bZPj")
.productSecret("aaaaaaaa")
.build();
}
@Override
public ThingDevice getDevice(String dn) {
return ThingDevice.builder()
.productKey("PjmkANSTDt85bZPj")
.deviceName(dn)
.build();
}
@Override
public Map<String, ?> getProperty(String dn) {
return new HashMap<>(0);
}
}

@ -0,0 +1,184 @@
package cc.iotkit.plugins.dlt645.service;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.DeviceState;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.actions.up.DeviceStateChange;
import cc.iotkit.plugin.core.thing.actions.up.PropertyReport;
import cc.iotkit.plugins.dlt645.analysis.DLT645Analysis;
import cc.iotkit.plugins.dlt645.analysis.DLT645Converter;
import cc.iotkit.plugins.dlt645.analysis.DLT645FunCode;
import cc.iotkit.plugins.dlt645.analysis.DLT645V2007Data;
import cc.iotkit.plugins.dlt645.conf.TcpClientConfig;
import cc.iotkit.plugins.dlt645.constants.DLT645Constant;
import cc.iotkit.plugins.dlt645.utils.ByteUtils;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.IdUtil;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetSocket;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Authortfd
* @Date2023/12/13 17:00
*/
@Slf4j
@Service
public class TcpClientVerticle extends AbstractVerticle {
@Getter
@Setter
private TcpClientConfig config;
private NetClient netClient;
private NetSocket socket;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
@Autowired
private PluginInfo pluginInfo;
private int connectState = 0;
private long timerID;
@Override
public void start() {
log.info("init start");
}
@Scheduled(initialDelay = 2, fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void initClient() {
if (connectState > 0) {
return;
}
connectState = 1;
DLT645Analysis.inst().getTemplateByDIn(DLT645Constant.PRO_VER_2007);
NetClientOptions options = new NetClientOptions();
options.setReconnectAttempts(Integer.MAX_VALUE);
options.setReconnectInterval(20000L);
netClient = vertx.createNetClient(options);
netClient.connect(config.getPort(), config.getHost())
.onComplete(result -> {
if (result.succeeded()) {
connectState = 2;
log.info("connect dlt645 server success");
socket = result.result();
stateChange(DeviceState.ONLINE);
socket.handler(data -> {
String hexStr = ByteUtils.byteArrayToHexString(data.getBytes(), false);
log.info("Received message:{}", hexStr);
Map<String, Object> ret = DLT645Analysis.unPackCmd2Map(ByteUtils.hexStringToByteArray(hexStr));
//获取功能码
Object func = ret.get(DLT645Analysis.FUN);
DLT645FunCode funCode = DLT645FunCode.decodeEntity((byte) func);
if (funCode.isError()) {
log.info("message erroe:{}", hexStr);
return;
}
//获取设备地址
byte[] adrrTmp = (byte[]) ret.get(DLT645Analysis.ADR);
byte[] addr = new byte[6];
ByteUtils.byteInvertedOrder(adrrTmp, addr);
//获取数据
byte[] dat = (byte[]) ret.get(DLT645Analysis.DAT);
String strAddr=ByteUtils.byteArrayToHexString(addr,false);
DLT645V2007Data dataEntity = new DLT645V2007Data();
dataEntity.decodeValue(dat, DLT645Analysis.din2entity);
Map<String, Object> params = new HashMap<>();
params.put("p" + dataEntity.getKey(), dataEntity.getValue());//数据标识
thingService.post(pluginInfo.getPluginId(),
PropertyReport.builder().deviceName(strAddr).productKey("PwMfpXmp4ZWkGahn")
.params(params)
.build()
);
}).closeHandler(res -> {
connectState = 0;
vertx.cancelTimer(timerID);
log.info("dlt645 tcp connection closed!");
stateChange(DeviceState.OFFLINE);
}
).exceptionHandler(res -> {
connectState = 0;
vertx.cancelTimer(timerID);
log.info("dlt645 tcp connection exce!");
stateChange(DeviceState.OFFLINE);
});
timerID = vertx.setPeriodic(config.getInterval(), t -> {
readDataTask();
});
} else {
connectState = 0;
log.info("connect dlt645 tcp error", result.cause());
}
})
.onFailure(e -> {
log.error("connect failed", e);
connectState = 0;
})
;
}
private void readDataTask() {
log.info("readData:" + socket);
if (socket != null) {
String msg = DLT645Converter.packData("000023092701", "读数据", "00000000");
sendMsg(msg);
}
}
@Override
public void stop() throws Exception {
if (netClient != null) {
netClient.close();
}
vertx.cancelTimer(timerID);
connectState = 0;
super.stop();
}
private void stateChange(DeviceState state) {
thingService.post(pluginInfo.getPluginId(),
applyPkDn(DeviceStateChange.builder()
.id(IdUtil.simpleUUID())
.state(state)
.time(System.currentTimeMillis())
.build()));
}
private IDeviceAction applyPkDn(IDeviceAction action) {
action.setProductKey("BRD3x4fkKxkaxXFt");
action.setDeviceName("WG123456");
return action;
}
public void sendMsg(String msg) {
log.info("send msg data:{}", msg);
Buffer data = Buffer.buffer(HexUtil.decodeHex(msg));
socket.write(data, r -> {
if (r.succeeded()) {
log.info("msg send success:{}", msg);
} else {
log.error("msg send failed", r.cause());
}
});
}
}

@ -0,0 +1,15 @@
package cc.iotkit.plugins.dlt645.utils;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
/**
* @Authortfd
* @Date2023/12/13 17:49
*/
@Getter(value = AccessLevel.PUBLIC)
@Setter(value = AccessLevel.PUBLIC)
public class ByteRef {
private byte value = 0;
}

@ -0,0 +1,78 @@
package cc.iotkit.plugins.dlt645.utils;
/**
* @Authortfd
* @Date2023/12/13 17:48
*/
public class ByteUtils {
/**
* byte
*
* @param hex 16 "FF"
* @return byte-1
*/
public static byte hex2byte(String hex) {
return Integer.valueOf(hex, 16).byteValue();
}
/**
* 16
*
* @param hexString 16
* @return
**/
public static byte[] hexStringToByteArray(String hexString) {
String string = hexString.replaceAll(" ", "");
final byte[] byteArray = new byte[string.length() / 2];
int pos = 0;
for (int i = 0; i < byteArray.length; i++) {
// 因为是16进制最多只会占用4位转换成字节需要两个16进制的字符高位在先
byte high = (byte) (Character.digit(string.charAt(pos), 16) & 0xff);
byte low = (byte) (Character.digit(string.charAt(pos + 1), 16) & 0xff);
byteArray[i] = (byte) (high << 4 | low);
pos += 2;
}
return byteArray;
}
/**
* 16
*
* @param byteArray
* @return 16
**/
public static String byteArrayToHexString(byte[] byteArray) {
return byteArrayToHexString(byteArray, true);
}
public static String byteArrayToHexString(byte[] byteArray, boolean blankz) {
final StringBuilder hexString = new StringBuilder();
for (int i = 0; i < byteArray.length; i++) {
if ((byteArray[i] & 0xff) < 0x10) {
// 0~F前面不零
hexString.append("0");
}
hexString.append(Integer.toHexString(0xFF & byteArray[i]));
if (blankz) {
hexString.append(" ");
}
}
return hexString.toString();
}
/**
*
*
**/
public static void byteInvertedOrder(byte[] tmp,byte[] retData) {
System.arraycopy(tmp, 0, retData, 0, Math.min(tmp.length, retData.length));
for (int i = 0; i < retData.length / 2; i++) {
byte by = retData[i];
retData[i] = retData[5 - i];
retData[5 - i] = by;
}
}
}

@ -0,0 +1,15 @@
package cc.iotkit.plugins.dlt645.utils;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
/**
* @Authortfd
* @Date2023/12/13 17:49
*/
@Getter(value = AccessLevel.PUBLIC)
@Setter(value = AccessLevel.PUBLIC)
public class BytesRef {
private byte[] value = new byte[] {};
}

@ -0,0 +1,424 @@
package cc.iotkit.plugins.dlt645.utils;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
/**
* @Authortfd
* @Date2023/12/13 17:50
*
*/
public class ContainerUtils {
public ContainerUtils() {
}
/**
*
* getMethodName(Integer.toHexString)toHexString
*
* @param function
* @param <E>
* @param <R>
* @return
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
private static <E, R> String getMethodName(SerializableFunction<E, R> function) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method method = function.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
String implMethodName = serializedLambda.getImplMethodName();
return implMethodName;
}
/**
*
*
* @param clazz
* @param function
* @param <E>
* @param <R>
* @param <T>
* @return
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
private static <E, R, T> Method getMethod(Class<T> clazz, SerializableFunction<E, R> function) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String methodName = ContainerUtils.getMethodName(function);
return clazz.getMethod(methodName);
}
/**
* getXxxx()
*
* @param objList
* @param clazz
* @param method methodclazz
* @param <K>
* @param <T>
* @return
*/
private static <K, T> List<K> buildListByGetField(List<T> objList, Class<K> clazz, Method method) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
List<K> keyList = new ArrayList<K>();
for (T obj : objList) {
// 接下来就该执行该方法了,第一个参数是具体调用该方法的对象, 第二个参数是执行该方法的具体参数
Object keyObject = method.invoke(obj);
if (clazz.isInstance(keyObject)) {
K key = clazz.cast(keyObject);
keyList.add(key);
}
}
return keyList;
}
public static <K, T> List<?> buildKeyList(List<T> objList, Method method) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
List<K> keyList = new ArrayList<K>();
Class<?> returnType = method.getReturnType();
for (T obj : objList) {
// 接下来就该执行该方法了,第一个参数是具体调用该方法的对象, 第二个参数是执行该方法的具体参数
Object keyObject = method.invoke(obj);
if (returnType.isInstance(keyObject)) {
keyList.add((K) returnType.cast(keyObject));
}
}
return keyList;
}
/**
* getXxxx()
*
* @param objList AClass
* @param clazz TClass AClass::getXxxx()TClass
* @param function AClass::getXxxx
* @param <E>
* @param <R>
* @param <K>
* @param <T>
* @return
*/
public static <E, R, K, T> List<K> buildListByGetField(List<T> objList, SerializableFunction<E, R> function, Class<K> clazz) {
if (objList.isEmpty()) {
return new ArrayList<K>();
}
try {
// 取得函数对应的方法
Method method = ContainerUtils.getMethod(objList.get(0).getClass(), function);
// 使用方法返回对应的数组
return ContainerUtils.buildListByGetField(objList, clazz, method);
} catch (NoSuchMethodException e) {
return new ArrayList<K>();
} catch (SecurityException e) {
return new ArrayList<K>();
} catch (IllegalAccessException e) {
return new ArrayList<K>();
} catch (IllegalArgumentException e) {
return new ArrayList<K>();
} catch (InvocationTargetException e) {
return new ArrayList<K>();
}
}
/**
* KeyMap
*
* @param objList
* @param clazz
* @param method 使obj.getClass().getMethod("getTnlKey", new Class[0])Method
* @return
*/
public static <K, T> Map<K, T> buildMapByKeyAndFinalMethod(List<T> objList, Class<K> clazz, Method method) {
try {
Map<K, T> uid2deviceMap = new HashMap<K, T>();
for (T obj : objList) {
// 接下来就该执行该方法了,第一个参数是具体调用该方法的对象, 第二个参数是执行该方法的具体参数
Object keyObject = method.invoke(obj);
if (clazz.isInstance(keyObject)) {
K key = clazz.cast(keyObject);
uid2deviceMap.put(key, obj);
}
}
return uid2deviceMap;
} catch (IllegalAccessException e) {
return Collections.emptyMap();
} catch (InvocationTargetException e) {
return Collections.emptyMap();
}
}
public static <E, R, K, T> Map<K, T> buildMapByKey(List<T> objList, SerializableFunction<E, R> function) {
if (objList.isEmpty()) {
return new HashMap<>();
}
try {
// 取得函数对应的方法
Method method = ContainerUtils.getMethod(objList.get(0).getClass(), function);
// 使用方法返回对应的数组
return ContainerUtils.buildMapByKey(objList, method);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | IllegalArgumentException |
SecurityException e) {
return new HashMap<K, T>();
}
}
public static <K, T> Map<K, T> buildMapByKey(List<T> objList, Method method) {
try {
Map<K, T> uid2deviceMap = new HashMap<K, T>();
for (T obj : objList) {
// 接下来就该执行该方法了,第一个参数是具体调用该方法的对象, 第二个参数是执行该方法的具体参数
Object keyObject = method.invoke(obj);
uid2deviceMap.put((K) keyObject, obj);
}
return uid2deviceMap;
} catch (IllegalAccessException | InvocationTargetException e) {
return Collections.emptyMap();
}
}
/**
*
*
* @param objList
* @param clazz
* @param method
* @param <K>
* @param <T>
* @return
*/
public static <K, T> Map<K, List<T>> buildMapByTypeAndFinalMethod(List<T> objList, Class<K> clazz, Method method) {
try {
Map<K, List<T>> uid2deviceMap = new HashMap<>();
for (T obj : objList) {
// 接下来就该执行该方法了,第一个参数是具体调用该方法的对象, 第二个参数是执行该方法的具体参数
Object keyObject = method.invoke(obj);
if (clazz.isInstance(keyObject)) {
K key = clazz.cast(keyObject);
List<T> list = uid2deviceMap.computeIfAbsent(key, k -> new ArrayList<>());
list.add(obj);
}
}
return uid2deviceMap;
} catch (IllegalAccessException | InvocationTargetException e) {
return Collections.emptyMap();
}
}
/**
* KeyMap
*
* @param objList AClass
* @param function AClassgetxxxx()
* @param clazz AClassBClass getxxxx()BClass
* @param <E>
* @param <R>
* @param <K>
* @param <T>
* @return
*/
public static <E, R, K, T> Map<K, T> buildMapByKeyAndFinalMethod(List<T> objList, SerializableFunction<E, R> function, Class<K> clazz) {
if (objList.isEmpty()) {
return new HashMap<>();
}
try {
// 取得函数对应的方法
Method method = ContainerUtils.getMethod(objList.get(0).getClass(), function);
// 使用方法返回对应的数组
return ContainerUtils.buildMapByKeyAndFinalMethod(objList, clazz, method);
} catch (NoSuchMethodException e) {
return new HashMap<K, T>();
} catch (SecurityException e) {
return new HashMap<K, T>();
} catch (IllegalAccessException e) {
return new HashMap<K, T>();
} catch (IllegalArgumentException e) {
return new HashMap<K, T>();
} catch (InvocationTargetException e) {
return new HashMap<K, T>();
}
}
public static <E, R, K, T> Map<K, List<T>> buildMapByTypeAndFinalMethod(List<T> objList, SerializableFunction<E, R> function, Class<K> clazz) {
if (objList.isEmpty()) {
return new HashMap<>();
}
try {
// 取得函数对应的方法
Method method = ContainerUtils.getMethod(objList.get(0).getClass(), function);
// 使用方法返回对应的数组
return ContainerUtils.buildMapByTypeAndFinalMethod(objList, clazz, method);
} catch (NoSuchMethodException | InvocationTargetException | IllegalArgumentException | IllegalAccessException |
SecurityException e) {
return new HashMap<>();
}
}
/**
* mapkeymap
*
* @param objList
* @param mapKey
* @param clazz
* @param <K>
* @param <T>
* @return
*/
public static <K, T> Map<K, Map<String, Object>> buildMapByMapAt(List<Map<String, Object>> objList, String mapKey, Class<K> clazz) {
if (objList.isEmpty()) {
return new HashMap<>();
}
Map<K, Map<String, Object>> result = new HashMap<>();
for (Map<String, Object> obj : objList) {
Object value = obj.get(mapKey);
if (!clazz.isInstance(value)) {
continue;
}
result.put((K) value, obj);
}
return result;
}
/**
*
*
* @param objList
* @param function
* @param key
* @param <E>
* @param <R>
* @param <K>
* @param <T>
* @return
*/
public static <E, R, K, T> T getObjectByKey(List<T> objList, SerializableFunction<E, R> function, K key) {
try {
if (objList.isEmpty()) {
return null;
}
Method method = ContainerUtils.getMethod(objList.get(0).getClass(), function);
for (T obj : objList) {
// 先获取相应的method对象,getMethod第一个参数是方法名第二个参数是该方法的参数类型
// Method method = obj.getClass().getMethod(getKeyMathName, new Class[0]);
// 接下来就该执行该方法了,第一个参数是具体调用该方法的对象, 第二个参数是执行该方法的具体参数
Object keyObject = method.invoke(obj);
if (key.getClass().isInstance(keyObject)) {
if (keyObject.equals(key)) {
return obj;
}
}
}
return null;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
return null;
}
}
/**
* Key-Value
*
* @param key2value key2value
* @return value2key
*/
public static <K, V> Map<V, K> exchange(Map<K, V> key2value) {
Map<V, K> result = new HashMap<>();
for (Map.Entry<K, V> entry : key2value.entrySet()) {
result.put(entry.getValue(), entry.getKey());
}
return result;
}
public static <K, V> Map<V, List<K>> exchanges(Map<K, V> key2value) {
Map<V, List<K>> result = new HashMap<>();
for (Map.Entry<K, V> entry : key2value.entrySet()) {
List<K> list = result.get(entry.getValue());
if (list == null) {
list = new ArrayList<>();
result.put(entry.getValue(), list);
}
list.add(entry.getKey());
}
return result;
}
/**
* Key
*
* @param key2value key2value
* @param keyList key
* @return keyvalues
*/
public static <K, V> List<V> buildValueListByKey(Map<K, V> key2value, Collection<K> keyList) {
List<V> resultList = new ArrayList<V>();
for (K key : keyList) {
V value = key2value.get(key);
if (value != null) {
resultList.add(value);
}
}
return resultList;
}
/**
* AB:A/B
*
* @param aClazzList
* @param bClazz
* @return
*/
public static <A, B> List<B> buildClassList(List<A> aClazzList, Class<B> bClazz) {
List<B> bInstanceList = new ArrayList<B>();
for (A aInstance : aClazzList) {
if (bClazz.isInstance(aInstance)) {
bInstanceList.add(bClazz.cast(aInstance));
}
}
return bInstanceList;
}
/**
*
*
* @param <E>
* @param <R>
*/
@FunctionalInterface
public interface SerializableFunction<E, R> extends Function<E, R>, Serializable {
}
}

@ -0,0 +1,143 @@
di1h,di1l,di0h,di0l,format,length,unit,read,write,name
9,0,1,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)正向有功总电能
9,0,2,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)反向有功总电能
9,1,1,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)正向无功总电能
9,1,2,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)反向无功总电能
9,1,3,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)一象限无功总电能
9,1,4,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)四象限无功总电能
9,1,5,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)二象限无功总电能
9,1,6,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)三象限无功总电能
9,4,1,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(上月)正向有功总电能
9,4,2,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(上月)反向有功总电能
9,5,1,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上月)正向无功总电能
9,5,2,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上月)反向无功总电能
9,5,3,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上月)一象限无功总电能
9,5,4,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上月)四象限无功总电能
9,5,5,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上月)二象限无功总电能
9,5,6,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上月)三象限无功总电能
9,8,1,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(上上月)正向有功总电能
9,8,2,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(上上月)反向有功总电能
9,9,1,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上上月)正向无功总电能
9,9,2,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上上月)反向无功总电能
9,9,3,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上上月)一象限无功总电能
9,9,4,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上上月)四象限无功总电能
9,9,5,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上上月)二象限无功总电能
9,9,6,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上上月)三象限无功总电能
A,0,1,0,XX.XXXX,3,kW,TRUE,FALSE,(当前)正向有功总最大需量
A,0,2,0,XX.XXXX,3,kW,TRUE,FALSE,(当前)反向有功总最大需量
A,1,1,0,XX.XXXX,3,kvar,TRUE,FALSE,(当前)正向无功总最大需量
A,1,2,0,XX.XXXX,3,kvar,TRUE,FALSE,(当前)反向无功总最大需量
A,1,3,0,XX.XXXX,3,kvar,TRUE,FALSE,(当前)一象限无功最大需量
A,1,4,0,XX.XXXX,3,kvar,TRUE,FALSE,(当前)四象限无功最大需量
A,1,5,0,XX.XXXX,3,kvar,TRUE,FALSE,(当前)二象限无功最大需量
A,1,6,0,XX.XXXX,3,kvar,TRUE,FALSE,(当前)三象限无功最大需量
A,4,1,0,XX.XXXX,3,kW,TRUE,FALSE,(上月)正向有功总最大需量
A,4,2,0,XX.XXXX,3,kW,TRUE,FALSE,(上月)反向有功总最大需量
A,5,1,0,XX.XXXX,3,kvar,TRUE,FALSE,(上月)正向无功总最大需量
A,5,2,0,XX.XXXX,3,kvar,TRUE,FALSE,(上月)反向无功总最大需量
A,5,3,0,XX.XXXX,3,kvar,TRUE,FALSE,(上月)一象限无功最大需量
A,5,4,0,XX.XXXX,3,kvar,TRUE,FALSE,(上月)四象限无功最大需量
A,5,5,0,XX.XXXX,3,kvar,TRUE,FALSE,(上月)二象限无功最大需量
A,5,6,0,XX.XXXX,3,kvar,TRUE,FALSE,(上月)三象限无功最大需量
A,8,1,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)正向有功总最大需量
A,8,2,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)反向有功总最大需量
A,9,1,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)正向无功总最大需量
A,9,2,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)反向无功总最大需量
A,9,3,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)一象限无功最大需量
A,9,4,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)四象限无功最大需量
A,9,5,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)二象限无功最大需量
A,9,6,0,XX.XXXX,3,kvar,TRUE,FALSE,(上上月)三象限无功最大需量
B,0,1,0,MMDDHHmm,4,,TRUE,FALSE,(当前)正向有功总最大需量发生时间
B,0,2,0,MMDDHHmm,4,,TRUE,FALSE,(当前)反向有功总最大需量发生时间
B,1,1,0,MMDDHHmm,4,,TRUE,FALSE,(当前)正向无功总最大需量发生时间
B,1,2,0,MMDDHHmm,4,,TRUE,FALSE,(当前)反向无功总最大需量发生时间
B,1,3,0,MMDDHHmm,4,,TRUE,FALSE,(当前)一象限无功最大需量发生时间
B,1,4,0,MMDDHHmm,4,,TRUE,FALSE,(当前)四象限无功最大需量发生时间
B,1,5,0,MMDDHHmm,4,,TRUE,FALSE,(当前)二象限无功最大需量发生时间
B,1,6,0,MMDDHHmm,4,,TRUE,FALSE,(当前)三象限无功最大需量发生时间
B,4,1,0,MMDDHHmm,4,,TRUE,FALSE,(上月)正向有功总最大需量发生时间
B,4,2,0,MMDDHHmm,4,,TRUE,FALSE,(上月)反向有功总最大需量发生时间
B,5,1,0,MMDDHHmm,4,,TRUE,FALSE,(上月)正向无功总最大需量发生时间
B,5,2,0,MMDDHHmm,4,,TRUE,FALSE,(上月)反向无功总最大需量发生时间
B,5,3,0,MMDDHHmm,4,,TRUE,FALSE,(上月)一象限无功最大需量发生时间
B,5,4,0,MMDDHHmm,4,,TRUE,FALSE,(上月)四象限无功最大需量发生时间
B,5,5,0,MMDDHHmm,4,,TRUE,FALSE,(上月)二象限无功最大需量发生时间
B,5,6,0,MMDDHHmm,4,,TRUE,FALSE,(上月)三象限无功最大需量发生时间
B,8,1,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)正向有功总最大需量发生时间
B,8,2,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)反向有功总最大需量发生时间
B,9,1,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)正向无功总最大需量发生时间
B,9,2,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)反向无功总最大需量发生时间
B,9,3,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)一象限无功最大需量发生时间
B,9,4,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)四象限无功最大需量发生时间
B,9,5,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)二象限无功最大需量发生时间
B,9,6,0,MMDDHHmm,4,,TRUE,FALSE,(上上月)三象限无功最大需量发生时间
B,2,1,0,MMDDHHmm,4,,TRUE,FALSE,最近一次编程时间
B,2,1,1,MMDDHHmm,4,,TRUE,FALSE,最近一次最大需量清零时间
B,2,1,2,NNNN,2,,TRUE,FALSE,编程次数
B,2,1,3,NNNN,2,,TRUE,FALSE,最大需量清零次数
B,2,1,4,NNNNNN,3,min,TRUE,FALSE,电池工作时间
B,3,1,0,NNNN,2,,TRUE,FALSE,总断相次数
B,3,1,1,NNNN,2,,TRUE,FALSE,A 相断相次数
B,3,1,2,NNNN,2,,TRUE,FALSE,B 相断相次数
B,3,1,3,NNNN,2,,TRUE,FALSE,C 相断相次数
B,3,2,0,NNNNNN,3,min,TRUE,FALSE,断相时间累计值
B,3,2,1,NNNNNN,3,min,TRUE,FALSE,A 断相时间累计值
B,3,2,2,NNNNNN,3,min,TRUE,FALSE,B 断相时间累计值
B,3,2,3,NNNNNN,3,min,TRUE,FALSE,C 断相时间累计值
B,3,3,0,MMDDHHmm,4,,TRUE,FALSE,最近一次断相起始时刻
B,3,3,1,MMDDHHmm,4,,TRUE,FALSE,A 相最近断相起始时刻
B,3,3,2,MMDDHHmm,4,,TRUE,FALSE,B 相最近断相起始时刻
B,3,3,3,MMDDHHmm,4,,TRUE,FALSE,C相最近断相起始时刻
B,3,4,0,MMDDHHmm,4,,TRUE,FALSE,最近一次断相的结束时刻
B,3,4,1,MMDDHHmm,4,,TRUE,FALSE,A 相最近一次断相的结束时刻
B,3,4,2,MMDDHHmm,4,,TRUE,FALSE,B 相最近一次断相的结束时刻
B,3,4,3,MMDDHHmm,4,,TRUE,FALSE,C 相最近一次断相的结束时刻
B,6,1,1,XXX,2,V,TRUE,FALSE,A相电压
B,6,1,2,XXX,2,V,TRUE,FALSE,B相电压
B,6,1,3,XXX,2,V,TRUE,FALSE,C相电压
B,6,2,1,XX.XX,2,A,TRUE,FALSE,A相电流
B,6,2,2,XX.XX,2,A,TRUE,FALSE,B相电流
B,6,2,3,XX.XX,2,A,TRUE,FALSE,C相电流
B,6,3,0,XX.XXXX,3,kW,TRUE,FALSE,瞬时有功功率
B,6,3,1,XX.XXXX,3,kW,TRUE,FALSE,A相有功功率
B,6,3,2,XX.XXXX,3,kW,TRUE,FALSE,B相有功功率
B,6,3,3,XX.XXXX,3,kW,TRUE,FALSE,C相有功功率
B,6,3,4,XX.XX,2,kW,TRUE,TRUE,正向有功功率上限值
B,6,3,5,XX.XX,2,kW,TRUE,TRUE,反向有功功率上限值
B,6,4,0,XX.XX,2,kvarh,TRUE,FALSE,瞬时无功功率
B,6,4,1,XX.XX,2,kvarh,TRUE,FALSE,A相无功功率
B,6,4,2,XX.XX,2,kvarh,TRUE,FALSE,B相无功功率
B,6,4,3,XX.XX,2,kvarh,TRUE,FALSE,C相无功功率
B,6,5,0,XX.XX,2,kvarh,TRUE,FALSE,总功率因数
B,6,5,1,XX.XX,3,kvarh,TRUE,FALSE,A 相功率因数
B,6,5,2,XX.XX,4,kvarh,TRUE,FALSE,B 相功率因数
B,6,5,3,XX.XX,5,kvarh,TRUE,FALSE,C 相功率因数
C,0,1,0,YYMMDDWW,4,,TRUE,TRUE,日期及周次
C,0,1,1,hhmmss,3,,TRUE,TRUE,时间
C,0,2,0,电表运行状态字,1,,TRUE,TRUE,电表运行状态字
C,0,2,1,电网状态字,1,,TRUE,TRUE,电网状态字
C,0,2,2,周休日状态字,1,,TRUE,TRUE,周休日状态字
C,0,3,0,NNNNNN,3,p/(kWh),TRUE,TRUE,电表常数(有功)
C,0,3,1,NNNNNN,3,p/(kvarh),TRUE,TRUE,电表常数(无功)
C,0,3,2,NN...NN,6,,TRUE,TRUE,表号
C,0,3,3,NN...NN,6,,TRUE,TRUE,用户号
C,0,3,4,NN...NN,6,,TRUE,TRUE,设备码
C,1,1,1,XX,1,min,TRUE,TRUE,最大需量周期
C,1,1,2,XX,1,min,TRUE,TRUE,滑差时间
C,1,1,3,XX,1,s,TRUE,TRUE,循显时间
C,1,1,4,XX,1,s,TRUE,TRUE,停显时间
C,1,1,5,NN,1,,TRUE,TRUE,显示电能小数位数
C,1,1,6,NN,1,,TRUE,TRUE,显示功率(最大需量)小数位数
C,1,1,7,DDhh,2,,TRUE,TRUE,自动抄表日期
C,1,1,8,NN,1,,TRUE,TRUE,负荷代表日
C,1,1,9,NNNNNN.N,4,kWh,TRUE,TRUE,有功电能起始读数
C,1,1,A,NNNNNN.N,4,kvarh,TRUE,TRUE,无功电能起始读数
C,2,1,1,NNNN,2,ms,TRUE,TRUE,输出脉冲宽度
C,2,1,2,NNNNNNNN,4,,FALSE,TRUE,密码权限及密码
C,3,1,0,NN,1,,TRUE,TRUE,年时区数
C,3,1,1,NN,1,,TRUE,TRUE,日时段表数
C,3,1,2,NN,1,,TRUE,TRUE,日时段(每日切换数)m≤10
C,3,1,3,NN,1,,TRUE,TRUE,费率数 k≤14
C,3,1,4,NN,1,,TRUE,TRUE,公共假日数
C,5,1,0,MMDDhhmm,4,,TRUE,TRUE,负荷记录起始时间
C,5,1,1,mmmm,2,min,TRUE,TRUE,负荷记录间隔时间
1 di1h di1l di0h di0l format length unit read write name
2 9 0 1 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)正向有功总电能
3 9 0 2 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)反向有功总电能
4 9 1 1 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)正向无功总电能
5 9 1 2 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)反向无功总电能
6 9 1 3 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)一象限无功总电能
7 9 1 4 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)四象限无功总电能
8 9 1 5 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)二象限无功总电能
9 9 1 6 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)三象限无功总电能
10 9 4 1 0 XXXXXX.XX 4 kWh TRUE FALSE (上月)正向有功总电能
11 9 4 2 0 XXXXXX.XX 4 kWh TRUE FALSE (上月)反向有功总电能
12 9 5 1 0 XXXXXX.XX 4 kvarh TRUE FALSE (上月)正向无功总电能
13 9 5 2 0 XXXXXX.XX 4 kvarh TRUE FALSE (上月)反向无功总电能
14 9 5 3 0 XXXXXX.XX 4 kvarh TRUE FALSE (上月)一象限无功总电能
15 9 5 4 0 XXXXXX.XX 4 kvarh TRUE FALSE (上月)四象限无功总电能
16 9 5 5 0 XXXXXX.XX 4 kvarh TRUE FALSE (上月)二象限无功总电能
17 9 5 6 0 XXXXXX.XX 4 kvarh TRUE FALSE (上月)三象限无功总电能
18 9 8 1 0 XXXXXX.XX 4 kWh TRUE FALSE (上上月)正向有功总电能
19 9 8 2 0 XXXXXX.XX 4 kWh TRUE FALSE (上上月)反向有功总电能
20 9 9 1 0 XXXXXX.XX 4 kvarh TRUE FALSE (上上月)正向无功总电能
21 9 9 2 0 XXXXXX.XX 4 kvarh TRUE FALSE (上上月)反向无功总电能
22 9 9 3 0 XXXXXX.XX 4 kvarh TRUE FALSE (上上月)一象限无功总电能
23 9 9 4 0 XXXXXX.XX 4 kvarh TRUE FALSE (上上月)四象限无功总电能
24 9 9 5 0 XXXXXX.XX 4 kvarh TRUE FALSE (上上月)二象限无功总电能
25 9 9 6 0 XXXXXX.XX 4 kvarh TRUE FALSE (上上月)三象限无功总电能
26 A 0 1 0 XX.XXXX 3 kW TRUE FALSE (当前)正向有功总最大需量
27 A 0 2 0 XX.XXXX 3 kW TRUE FALSE (当前)反向有功总最大需量
28 A 1 1 0 XX.XXXX 3 kvar TRUE FALSE (当前)正向无功总最大需量
29 A 1 2 0 XX.XXXX 3 kvar TRUE FALSE (当前)反向无功总最大需量
30 A 1 3 0 XX.XXXX 3 kvar TRUE FALSE (当前)一象限无功最大需量
31 A 1 4 0 XX.XXXX 3 kvar TRUE FALSE (当前)四象限无功最大需量
32 A 1 5 0 XX.XXXX 3 kvar TRUE FALSE (当前)二象限无功最大需量
33 A 1 6 0 XX.XXXX 3 kvar TRUE FALSE (当前)三象限无功最大需量
34 A 4 1 0 XX.XXXX 3 kW TRUE FALSE (上月)正向有功总最大需量
35 A 4 2 0 XX.XXXX 3 kW TRUE FALSE (上月)反向有功总最大需量
36 A 5 1 0 XX.XXXX 3 kvar TRUE FALSE (上月)正向无功总最大需量
37 A 5 2 0 XX.XXXX 3 kvar TRUE FALSE (上月)反向无功总最大需量
38 A 5 3 0 XX.XXXX 3 kvar TRUE FALSE (上月)一象限无功最大需量
39 A 5 4 0 XX.XXXX 3 kvar TRUE FALSE (上月)四象限无功最大需量
40 A 5 5 0 XX.XXXX 3 kvar TRUE FALSE (上月)二象限无功最大需量
41 A 5 6 0 XX.XXXX 3 kvar TRUE FALSE (上月)三象限无功最大需量
42 A 8 1 0 XX.XXXX 3 kvar TRUE FALSE (上上月)正向有功总最大需量
43 A 8 2 0 XX.XXXX 3 kvar TRUE FALSE (上上月)反向有功总最大需量
44 A 9 1 0 XX.XXXX 3 kvar TRUE FALSE (上上月)正向无功总最大需量
45 A 9 2 0 XX.XXXX 3 kvar TRUE FALSE (上上月)反向无功总最大需量
46 A 9 3 0 XX.XXXX 3 kvar TRUE FALSE (上上月)一象限无功最大需量
47 A 9 4 0 XX.XXXX 3 kvar TRUE FALSE (上上月)四象限无功最大需量
48 A 9 5 0 XX.XXXX 3 kvar TRUE FALSE (上上月)二象限无功最大需量
49 A 9 6 0 XX.XXXX 3 kvar TRUE FALSE (上上月)三象限无功最大需量
50 B 0 1 0 MMDDHHmm 4 TRUE FALSE (当前)正向有功总最大需量发生时间
51 B 0 2 0 MMDDHHmm 4 TRUE FALSE (当前)反向有功总最大需量发生时间
52 B 1 1 0 MMDDHHmm 4 TRUE FALSE (当前)正向无功总最大需量发生时间
53 B 1 2 0 MMDDHHmm 4 TRUE FALSE (当前)反向无功总最大需量发生时间
54 B 1 3 0 MMDDHHmm 4 TRUE FALSE (当前)一象限无功最大需量发生时间
55 B 1 4 0 MMDDHHmm 4 TRUE FALSE (当前)四象限无功最大需量发生时间
56 B 1 5 0 MMDDHHmm 4 TRUE FALSE (当前)二象限无功最大需量发生时间
57 B 1 6 0 MMDDHHmm 4 TRUE FALSE (当前)三象限无功最大需量发生时间
58 B 4 1 0 MMDDHHmm 4 TRUE FALSE (上月)正向有功总最大需量发生时间
59 B 4 2 0 MMDDHHmm 4 TRUE FALSE (上月)反向有功总最大需量发生时间
60 B 5 1 0 MMDDHHmm 4 TRUE FALSE (上月)正向无功总最大需量发生时间
61 B 5 2 0 MMDDHHmm 4 TRUE FALSE (上月)反向无功总最大需量发生时间
62 B 5 3 0 MMDDHHmm 4 TRUE FALSE (上月)一象限无功最大需量发生时间
63 B 5 4 0 MMDDHHmm 4 TRUE FALSE (上月)四象限无功最大需量发生时间
64 B 5 5 0 MMDDHHmm 4 TRUE FALSE (上月)二象限无功最大需量发生时间
65 B 5 6 0 MMDDHHmm 4 TRUE FALSE (上月)三象限无功最大需量发生时间
66 B 8 1 0 MMDDHHmm 4 TRUE FALSE (上上月)正向有功总最大需量发生时间
67 B 8 2 0 MMDDHHmm 4 TRUE FALSE (上上月)反向有功总最大需量发生时间
68 B 9 1 0 MMDDHHmm 4 TRUE FALSE (上上月)正向无功总最大需量发生时间
69 B 9 2 0 MMDDHHmm 4 TRUE FALSE (上上月)反向无功总最大需量发生时间
70 B 9 3 0 MMDDHHmm 4 TRUE FALSE (上上月)一象限无功最大需量发生时间
71 B 9 4 0 MMDDHHmm 4 TRUE FALSE (上上月)四象限无功最大需量发生时间
72 B 9 5 0 MMDDHHmm 4 TRUE FALSE (上上月)二象限无功最大需量发生时间
73 B 9 6 0 MMDDHHmm 4 TRUE FALSE (上上月)三象限无功最大需量发生时间
74 B 2 1 0 MMDDHHmm 4 TRUE FALSE 最近一次编程时间
75 B 2 1 1 MMDDHHmm 4 TRUE FALSE 最近一次最大需量清零时间
76 B 2 1 2 NNNN 2 TRUE FALSE 编程次数
77 B 2 1 3 NNNN 2 TRUE FALSE 最大需量清零次数
78 B 2 1 4 NNNNNN 3 min TRUE FALSE 电池工作时间
79 B 3 1 0 NNNN 2 TRUE FALSE 总断相次数
80 B 3 1 1 NNNN 2 TRUE FALSE A 相断相次数
81 B 3 1 2 NNNN 2 TRUE FALSE B 相断相次数
82 B 3 1 3 NNNN 2 TRUE FALSE C 相断相次数
83 B 3 2 0 NNNNNN 3 min TRUE FALSE 断相时间累计值
84 B 3 2 1 NNNNNN 3 min TRUE FALSE A 断相时间累计值
85 B 3 2 2 NNNNNN 3 min TRUE FALSE B 断相时间累计值
86 B 3 2 3 NNNNNN 3 min TRUE FALSE C 断相时间累计值
87 B 3 3 0 MMDDHHmm 4 TRUE FALSE 最近一次断相起始时刻
88 B 3 3 1 MMDDHHmm 4 TRUE FALSE A 相最近断相起始时刻
89 B 3 3 2 MMDDHHmm 4 TRUE FALSE B 相最近断相起始时刻
90 B 3 3 3 MMDDHHmm 4 TRUE FALSE C相最近断相起始时刻
91 B 3 4 0 MMDDHHmm 4 TRUE FALSE 最近一次断相的结束时刻
92 B 3 4 1 MMDDHHmm 4 TRUE FALSE A 相最近一次断相的结束时刻
93 B 3 4 2 MMDDHHmm 4 TRUE FALSE B 相最近一次断相的结束时刻
94 B 3 4 3 MMDDHHmm 4 TRUE FALSE C 相最近一次断相的结束时刻
95 B 6 1 1 XXX 2 V TRUE FALSE A相电压
96 B 6 1 2 XXX 2 V TRUE FALSE B相电压
97 B 6 1 3 XXX 2 V TRUE FALSE C相电压
98 B 6 2 1 XX.XX 2 A TRUE FALSE A相电流
99 B 6 2 2 XX.XX 2 A TRUE FALSE B相电流
100 B 6 2 3 XX.XX 2 A TRUE FALSE C相电流
101 B 6 3 0 XX.XXXX 3 kW TRUE FALSE 瞬时有功功率
102 B 6 3 1 XX.XXXX 3 kW TRUE FALSE A相有功功率
103 B 6 3 2 XX.XXXX 3 kW TRUE FALSE B相有功功率
104 B 6 3 3 XX.XXXX 3 kW TRUE FALSE C相有功功率
105 B 6 3 4 XX.XX 2 kW TRUE TRUE 正向有功功率上限值
106 B 6 3 5 XX.XX 2 kW TRUE TRUE 反向有功功率上限值
107 B 6 4 0 XX.XX 2 kvarh TRUE FALSE 瞬时无功功率
108 B 6 4 1 XX.XX 2 kvarh TRUE FALSE A相无功功率
109 B 6 4 2 XX.XX 2 kvarh TRUE FALSE B相无功功率
110 B 6 4 3 XX.XX 2 kvarh TRUE FALSE C相无功功率
111 B 6 5 0 XX.XX 2 kvarh TRUE FALSE 总功率因数
112 B 6 5 1 XX.XX 3 kvarh TRUE FALSE A 相功率因数
113 B 6 5 2 XX.XX 4 kvarh TRUE FALSE B 相功率因数
114 B 6 5 3 XX.XX 5 kvarh TRUE FALSE C 相功率因数
115 C 0 1 0 YYMMDDWW 4 TRUE TRUE 日期及周次
116 C 0 1 1 hhmmss 3 TRUE TRUE 时间
117 C 0 2 0 电表运行状态字 1 TRUE TRUE 电表运行状态字
118 C 0 2 1 电网状态字 1 TRUE TRUE 电网状态字
119 C 0 2 2 周休日状态字 1 TRUE TRUE 周休日状态字
120 C 0 3 0 NNNNNN 3 p/(kWh) TRUE TRUE 电表常数(有功)
121 C 0 3 1 NNNNNN 3 p/(kvarh) TRUE TRUE 电表常数(无功)
122 C 0 3 2 NN...NN 6 TRUE TRUE 表号
123 C 0 3 3 NN...NN 6 TRUE TRUE 用户号
124 C 0 3 4 NN...NN 6 TRUE TRUE 设备码
125 C 1 1 1 XX 1 min TRUE TRUE 最大需量周期
126 C 1 1 2 XX 1 min TRUE TRUE 滑差时间
127 C 1 1 3 XX 1 s TRUE TRUE 循显时间
128 C 1 1 4 XX 1 s TRUE TRUE 停显时间
129 C 1 1 5 NN 1 TRUE TRUE 显示电能小数位数
130 C 1 1 6 NN 1 TRUE TRUE 显示功率(最大需量)小数位数
131 C 1 1 7 DDhh 2 TRUE TRUE 自动抄表日期
132 C 1 1 8 NN 1 TRUE TRUE 负荷代表日
133 C 1 1 9 NNNNNN.N 4 kWh TRUE TRUE 有功电能起始读数
134 C 1 1 A NNNNNN.N 4 kvarh TRUE TRUE 无功电能起始读数
135 C 2 1 1 NNNN 2 ms TRUE TRUE 输出脉冲宽度
136 C 2 1 2 NNNNNNNN 4 FALSE TRUE 密码权限及密码
137 C 3 1 0 NN 1 TRUE TRUE 年时区数
138 C 3 1 1 NN 1 TRUE TRUE 日时段表数
139 C 3 1 2 NN 1 TRUE TRUE 日时段(每日切换数)m≤10
140 C 3 1 3 NN 1 TRUE TRUE 费率数 k≤14
141 C 3 1 4 NN 1 TRUE TRUE 公共假日数
142 C 5 1 0 MMDDhhmm 4 TRUE TRUE 负荷记录起始时间
143 C 5 1 1 mmmm 2 min TRUE TRUE 负荷记录间隔时间

@ -0,0 +1,425 @@
di3,di2,di1,di0,format,length,unit,read,write,name
0,0,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)组合有功总电能
0,1,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)正向有功总电能
0,2,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)反向有功总电能
0,3,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)组合无功1总电能
0,4,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)组合无功2总电能
0,5,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)第一象限无功总电能
0,6,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)第二象限无功总电能
0,7,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)第三象限无功总电能
0,8,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)第四象限无功总电能
0,9,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)正向视在总电能
0,A,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)反向视在总电能
0,B,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,当前结算周期组合有功总累计用电量
0,80,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)关联总电能
0,81,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)正向有功基波总电能
0,82,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)反向有功基波总电能
0,83,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)正向有功谐波总电能
0,84,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)反向有功谐波总电能
0,85,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)铜损有功总电能补偿量
0,86,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)铁损有功总电能补偿量
0,15,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)A相正向有功电能
0,16,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)A相反向有功电能
0,17,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)A相组合无功1电能
0,18,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)A相组合无功2电能
0,19,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)A相第一象限无功电能
0,1A,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)A相第二象限无功电能
0,1B,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)A相第三象限无功电能
0,1C,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)A相第四象限无功电能
0,1D,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)A相正向视在电能
0,1E,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)A相反向视在电能
0,29,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)B相正向有功电能
0,2A,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)B相反向有功电能
0,2B,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)B相组合无功1电能
0,2C,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)B相组合无功2电能
0,2D,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)B相第一象限无功电能
0,2E,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)B相第二象限无功电能
0,2F,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)B相第三象限无功电能
0,30,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)B相第四象限无功电能
0,31,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)B相正向视在电能
0,32,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)B相反向视在电能
0,3D,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)C相正向有功电能
0,3E,0,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)C相反向有功电能
0,3F,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)C相组合无功1电能
0,40,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)C相组合无功2电能
0,41,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)C相第一象限无功电能
0,42,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)C相第二象限无功电能
0,43,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)C相第三象限无功电能
0,44,0,0,XXXXXX.XX,4,kvarh,TRUE,FALSE,(当前)C相第四象限无功电能
0,45,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)C相正向视在电能
0,46,0,0,XXXXXX.XX,4,kVAh,TRUE,FALSE,(当前)C相反向视在电能
0,0,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)组合有功总电能
0,1,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)正向有功总电能
0,2,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)反向有功总电能
0,3,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)组合无功1总电能
0,4,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)组合无功2总电能
0,5,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)第一象限无功总电能
0,6,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)第二象限无功总电能
0,7,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)第三象限无功总电能
0,8,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)第四象限无功总电能
0,9,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)正向视在总电能
0,A,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)反向视在总电能
0,80,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)关联总电能
0,81,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)正向有功基波总电能
0,82,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)反向有功基波总电能
0,83,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)正向有功谐波总电能
0,84,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)反向有功谐波总电能
0,85,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)铜损有功总电能补偿量
0,86,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)铁损有功总电能补偿量
0,15,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)A相正向有功电能
0,16,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)A相反向有功电能
0,17,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)A相组合无功1电能
0,18,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)A相组合无功2电能
0,19,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)A相第一象限无功电能
0,1A,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)A相第二象限无功电能
0,1B,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)A相第三象限无功电能
0,1C,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)A相第四象限无功电能
0,1D,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)A相正向视在电能
0,1E,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)A相反向视在电能
0,29,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)B相正向有功电能
0,2A,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)B相反向有功电能
0,2B,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)B相组合无功1电能
0,2C,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)B相组合无功2电能
0,2D,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)B相第一象限无功电能
0,2E,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)B相第二象限无功电能
0,2F,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)B相第三象限无功电能
0,30,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)B相第四象限无功电能
0,31,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)B相正向视在电能
0,32,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)B相反向视在电能
0,3D,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)C相正向有功电能
0,3E,0,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(上1结算日)C相反向有功电能
0,3F,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)C相组合无功1电能
0,40,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)C相组合无功2电能
0,41,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)C相第一象限无功电能
0,42,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)C相第二象限无功电能
0,43,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)C相第三象限无功电能
0,44,0,1,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上1结算日)C相第四象限无功电能
0,45,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)C相正向视在电能
0,46,0,1,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上1结算日)C相反向视在电能
0,0,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)组合有功总电能
0,1,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)正向有功总电能
0,2,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)反向有功总电能
0,3,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)组合无功1总电能
0,4,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)组合无功2总电能
0,5,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)第一象限无功总电能
0,6,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)第二象限无功总电能
0,7,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)第三象限无功总电能
0,8,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)第四象限无功总电能
0,9,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)正向视在总电能
0,A,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)反向视在总电能
0,80,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)关联总电能
0,81,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)正向有功基波总电能
0,82,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)反向有功基波总电能
0,83,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)正向有功谐波总电能
0,84,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)反向有功谐波总电能
0,85,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)铜损有功总电能补偿量
0,86,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)铁损有功总电能补偿量
0,15,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相正向有功电能
0,16,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相反向有功电能
0,17,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)A相组合无功1电能
0,18,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)A相组合无功2电能
0,19,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)A相第一象限无功电能
0,1A,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)A相第二象限无功电能
0,1B,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)A相第三象限无功电能
0,1C,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)A相第四象限无功电能
0,1D,0,C,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上12结算日)A相正向视在电能
0,1E,0,C,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上12结算日)A相反向视在电能
0,9,1,0,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)剩余电量
0,9,1,1,XXXXXX.XX,4,kWh,TRUE,FALSE,(当前)透支电量
0,9,2,0,XXXXXX.XX,4,元,TRUE,FALSE,(当前)剩余金额
0,9,2,1,XXXXXX.XX,4,元,TRUE,FALSE,(当前)透支金额
0,94,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相关联电能
0,95,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相正向有功基波电能
0,96,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相反向有功基波电能
0,97,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相正向有功谐波电能
0,98,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相反向有功谐波电能
0,99,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相铜损有功电能补偿量
0,9A,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)A相铁损有功电能补偿量
0,29,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)B相正向有功电能
0,2A,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)B相反向有功电能
0,2B,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)B相组合无功1电能
0,2C,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)B相组合无功2电能
0,2D,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)B相第一象限无功电能
0,2E,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)B相第二象限无功电能
0,2F,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)B相第三象限无功电能
0,30,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)B相第四象限无功电能
0,31,0,C,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上12结算日)B相正向视在电能
0,32,0,C,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上12结算日)B相反向视在电能
0,3D,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)C相正向有功电能
0,3E,0,C,XXXXXX.XX,4,kWh,TRUE,FALSE,(上12结算日)C相反向有功电能
0,3F,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)C相组合无功1电能
0,40,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)C相组合无功2电能
0,41,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)C相第一象限无功电能
0,42,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)C相第二象限无功电能
0,43,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)C相第三象限无功电能
0,44,0,C,XXXXXX.XX,4,kvarh,TRUE,FALSE,(上12结算日)C相第四象限无功电能
0,45,0,C,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上12结算日)C相正向视在电能
0,46,0,C,XXXXXX.XX,4,kVAh,TRUE,FALSE,(上12结算日)C相反向视在电能
1,1,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)正向有功总最大需量及发生时间
1,2,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)反向有功总最大需量及发生时间
1,3,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)组合无功1总最大需量及发生时间
1,4,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)组合无功2总最大需量及发生时间
1,5,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)第一象限无功总最大需量及发生时间
1,6,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)第二象限无功总最大需量及发生时间
1,7,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)第三象限无功总最大需量及发生时间
1,8,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)第四象限无功总最大需量及发生时间
1,9,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)正向视在总最大需量及发生时间
1,A,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)反向视在总最大需量及发生时间
1,15,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)A相正向有功最大需量及发生时间
1,16,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)A相反向有功最大需量及发生时间
1,17,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)A相组合无功1最大需量及发生时间
1,18,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)A相组合无功2最大需量及发生时间
1,19,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)A相第一象限无功最大需量及发生时间
1,1A,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)A相第二象限无功最大需量及发生时间
1,1B,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)A相第三象限无功最大需量及发生时间
1,1C,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)A相第四象限无功最大需量及发生时间
1,1D,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)A相正向视在最大需量及发生时间
1,1E,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)A相反向视在最大需量及发生时间
1,29,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)B相正向有功最大需量及发生时间
1,2A,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)B相反向有功最大需量及发生时间
1,2B,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)B相组合无功1最大需量及发生时间
1,2C,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)B相组合无功2最大需量及发生时间
1,2D,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)B相第一象限无功最大需量及发生时间
1,2E,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)B相第二象限无功最大需量及发生时间
1,2F,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)B相第三象限无功最大需量及发生时间
1,30,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)B相第四象限无功最大需量及发生时间
1,31,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)B相正向视在最大需量及发生时间
1,32,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)B相反向视在最大需量及发生时间
1,3D,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)C相正向有功最大需量及发生时间
1,3E,0,0,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(当前)C相反向有功最大需量及发生时间
1,3F,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)C相组合无功1最大需量及发生时间
1,40,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)C相组合无功2最大需量及发生时间
1,41,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)C相第一象限无功最大需量及发生时间
1,42,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)C相第二象限无功最大需量及发生时间
1,43,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)C相第三象限无功最大需量及发生时间
1,44,0,0,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(当前)C相第四象限无功最大需量及发生时间
1,45,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)C相正向视在最大需量及发生时间
1,46,0,0,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(当前)C相反向视在最大需量及发生时间
1,1,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)正向有功总最大需量及发生时间
1,2,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)反向有功总最大需量及发生时间
1,3,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)组合无功1总最大需量及发生时间
1,4,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)组合无功2总最大需量及发生时间
1,5,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)第一象限无功总最大需量及发生时间
1,6,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)第二象限无功总最大需量及发生时间
1,7,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)第三象限无功总最大需量及发生时间
1,8,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)第四象限无功总最大需量及发生时间
1,9,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)正向视在总最大需量及发生时间
1,A,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)反向视在总最大需量及发生时间
1,15,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)A相正向有功最大需量及发生时间
1,16,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)A相反向有功最大需量及发生时间
1,17,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)A相组合无功1最大需量及发生时间
1,18,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)A相组合无功2最大需量及发生时间
1,19,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)A相第一象限无功最大需量及发生时间
1,1A,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)A相第二象限无功最大需量及发生时间
1,1B,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)A相第三象限无功最大需量及发生时间
1,1C,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)A相第四象限无功最大需量及发生时间
1,1D,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)A相正向视在最大需量及发生时间
1,1E,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)A相反向视在最大需量及发生时间
1,29,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)B相正向有功最大需量及发生时间
1,2A,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)B相反向有功最大需量及发生时间
1,2B,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)B相组合无功1最大需量及发生时间
1,2C,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)B相组合无功2最大需量及发生时间
1,2D,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)B相第一象限无功最大需量及发生时间
1,2E,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)B相第二象限无功最大需量及发生时间
1,2F,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)B相第三象限无功最大需量及发生时间
1,30,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)B相第四象限无功最大需量及发生时间
1,31,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)B相正向视在最大需量及发生时间
1,32,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)B相反向视在最大需量及发生时间
1,3D,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)C相正向有功最大需量及发生时间
1,3E,0,1,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上1结算日)C相反向有功最大需量及发生时间
1,3F,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)C相组合无功1最大需量及发生时间
1,40,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)C相组合无功2最大需量及发生时间
1,41,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)C相第一象限无功最大需量及发生时间
1,42,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)C相第二象限无功最大需量及发生时间
1,43,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)C相第三象限无功最大需量及发生时间
1,44,0,1,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上1结算日)C相第四象限无功最大需量及发生时间
1,45,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)C相正向视在最大需量及发生时间
1,46,0,1,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上1结算日)C相反向视在最大需量及发生时间
1,1,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)正向有功总最大需量及发生时间
1,2,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)反向有功总最大需量及发生时间
1,3,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)组合无功1总最大需量及发生时间
1,4,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)组合无功2总最大需量及发生时间
1,5,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)第一象限无功总最大需量及发生时间
1,6,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)第二象限无功总最大需量及发生时间
1,7,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)第三象限无功总最大需量及发生时间
1,8,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)第四象限无功总最大需量及发生时间
1,9,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)正向视在总最大需量及发生时间
1,A,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)反向视在总最大需量及发生时间
1,15,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)A相正向有功最大需量及发生时间
1,16,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)A相反向有功最大需量及发生时间
1,17,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)A相组合无功1最大需量及发生时间
1,18,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)A相组合无功2最大需量及发生时间
1,19,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)A相第一象限无功最大需量及发生时间
1,1A,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)A相第二象限无功最大需量及发生时间
1,1B,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)A相第三象限无功最大需量及发生时间
1,1C,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)A相第四象限无功最大需量及发生时间
1,1D,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)A相正向视在最大需量及发生时间
1,1E,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)A相反向视在最大需量及发生时间
1,29,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)B相正向有功最大需量及发生时间
1,2A,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)B相反向有功最大需量及发生时间
1,2B,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)B相组合无功1最大需量及发生时间
1,2C,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)B相组合无功2最大需量及发生时间
1,2D,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)B相第一象限无功最大需量及发生时间
1,2E,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)B相第二象限无功最大需量及发生时间
1,2F,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)B相第三象限无功最大需量及发生时间
1,30,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)B相第四象限无功最大需量及发生时间
1,31,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)B相正向视在最大需量及发生时间
1,32,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)B相反向视在最大需量及发生时间
1,3D,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)C相正向有功最大需量及发生时间
1,3E,0,C,XX.XXXX|YYMMDDhhmm,8,kW,TRUE,FALSE,(上12结算日)C相反向有功最大需量及发生时间
1,3F,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)C相组合无功1最大需量及发生时间
1,40,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)C相组合无功2最大需量及发生时间
1,41,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)C相第一象限无功最大需量及发生时间
1,42,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)C相第二象限无功最大需量及发生时间
1,43,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)C相第三象限无功最大需量及发生时间
1,44,0,C,XX.XXXX|YYMMDDhhmm,8,kvar,TRUE,FALSE,(上12结算日)C相第四象限无功最大需量及发生时间
1,45,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)C相正向视在最大需量及发生时间
1,46,0,C,XX.XXXX|YYMMDDhhmm,8,kVA,TRUE,FALSE,(上12结算日)C相反向视在最大需量及发生时间
2,1,1,0,XXX.X,2,V,TRUE,FALSE,A相电压
2,1,2,0,XXX.X,2,V,TRUE,FALSE,B相电压
2,1,3,0,XXX.X,2,V,TRUE,FALSE,C相电压
2,2,1,0,XXX.XXX,3,A,TRUE,FALSE,A相电流
2,2,2,0,XXX.XXX,3,A,TRUE,FALSE,B相电流
2,2,3,0,XXX.XXX,3,A,TRUE,FALSE,C相电流
2,3,0,0,XXX.XXX,3,kW,TRUE,FALSE,瞬时总有功功率
2,3,1,0,XXX.XXX,3,kW,TRUE,FALSE,瞬时A相有功功率
2,3,2,0,XXX.XXX,3,kW,TRUE,FALSE,瞬时B相有功功率
2,3,3,0,XXX.XXX,3,kW,TRUE,FALSE,瞬时C相有功功率
2,4,0,0,XXX.XXX,3,kvar ,TRUE,FALSE,瞬时总无功功率
2,4,1,0,XXX.XXX,3,kvar ,TRUE,FALSE,瞬时A相无功功率
2,4,2,0,XXX.XXX,3,kvar ,TRUE,FALSE,瞬时B相无功功率
2,4,3,0,XXX.XXX,3,kvar ,TRUE,FALSE,瞬时C相无功功率
2,5,0,0,XXX.XXX,3,kVA,TRUE,FALSE,瞬时总视在功率
2,5,1,0,XXX.XXX,3,kVA,TRUE,FALSE,瞬时A相视在功率
2,5,2,0,XXX.XXX,3,kVA,TRUE,FALSE,瞬时B相视在功率
2,5,3,0,XXX.XXX,3,kVA,TRUE,FALSE,瞬时C相视在功率
2,6,0,0,X.XXX,2,kVA,TRUE,FALSE,总功率因数
2,6,1,0,X.XXX,2,kVA,TRUE,FALSE,A相功率因数
2,6,2,0,X.XXX,2,kVA,TRUE,FALSE,B相功率因数
2,6,3,0,X.XXX,2,kVA,TRUE,FALSE,C相功率因数
2,7,1,0,X.XXX,2,°,TRUE,FALSE,A相相角
2,7,2,0,X.XXX,2,°,TRUE,FALSE,B相相角
2,7,3,0,X.XXX,2,°,TRUE,FALSE,C相相角
2,8,1,0,X.XXX,2,% ,TRUE,FALSE,A相电压波形失真度
2,8,2,0,X.XXX,2,% ,TRUE,FALSE,B相电压波形失真度
2,8,3,0,X.XXX,2,% ,TRUE,FALSE,C相电压波形失真度
2,9,1,0,X.XXX,2,% ,TRUE,FALSE,A相电流波形失真度
2,9,2,0,X.XXX,2,% ,TRUE,FALSE,B相电流波形失真度
2,9,3,0,X.XXX,2,% ,TRUE,FALSE,C相电流波形失真度
2,A,1,0,X.XXX,2,% ,TRUE,FALSE,A相电压1次谐波含量
2,A,2,0,X.XXX,2,% ,TRUE,FALSE,B相电压1次谐波含量
2,A,3,0,X.XXX,2,% ,TRUE,FALSE,C相电压1次谐波含量
2,B,1,0,X.XXX,2,% ,TRUE,FALSE,A相电流1次谐波含量
2,B,2,0,X.XXX,2,% ,TRUE,FALSE,B相电流1次谐波含量
2,B,3,0,X.XXX,2,% ,TRUE,FALSE,C相电流1次谐波含量
2,80,0,1,XXX.XXX,3,A,TRUE,FALSE,零线电流
2,80,0,2,XX.XX,2,Hz,TRUE,FALSE,电网频率
2,80,0,3,XX.XXXX,3,kW,TRUE,FALSE,一分钟有功总平均功率
2,80,0,4,XX.XXXX,3,kW,TRUE,FALSE,当前有功需量
2,80,0,5,XX.XXXX,3,kvar,TRUE,FALSE,当前无功需量
2,80,0,6,XX.XXXX,3,kVA,TRUE,FALSE,当前视在需量
2,80,0,7,XXX.X,2,℃,TRUE,FALSE,表内温度
2,80,0,8,XX.XX,2,V,TRUE,FALSE,时钟电池电压(内部)
2,80,0,9,XX.XX,2,V,TRUE,FALSE,停电抄表电池电压(外部)
2,80,0,0A,XXXXXXXX,4,分,TRUE,FALSE,内部电池工作时间
2,80,0,0B,XXXX.XXXX,4,元/kWh,TRUE,FALSE,当前阶梯电价
4,0,1,1,YYMMDDWW,4,年月日星期,TRUE,TRUE,日期及星期(其中0代表星期天)
4,0,1,2,hhmmss,3,时分秒,TRUE,TRUE,时间
4,0,1,3,NN,1,分,TRUE,TRUE,最大需量周期
4,0,1,4,NN,1,分,TRUE,TRUE,滑差时间
4,0,1,5,XXXX,2,毫秒,TRUE,TRUE,校表脉冲宽度
4,0,1,6,YYMMDDhhmm,5,年月日时分,TRUE,TRUE,两套时区表切换时间
4,0,1,7,YYMMDDhhmm,5,年月日时分,TRUE,TRUE,两套日时段表切换时间
4,0,1,8,YYMMDDhhmm,5,年月日时分,TRUE,TRUE,两套费率电价切换时间
4,0,1,9,YYMMDDhhmm,5,年月日时分,TRUE,TRUE,两套阶梯切换时间
4,0,2,1,NN,1,个,TRUE,TRUE,年时区数p≤14
4,0,2,2,NN,1,个,TRUE,TRUE,日时段表数q≤8
4,0,2,3,NN,1,个,TRUE,TRUE,日时段数(每日切换数)m≤14
4,0,2,4,NN,1,个,TRUE,TRUE,费率数k≤63
4,0,2,5,NNNN,2,个,TRUE,TRUE,公共假日数n≤254
4,0,2,6,NN,1,次,TRUE,TRUE,谐波分析次数
4,0,2,7,NN,1,次,TRUE,TRUE,阶梯数
4,0,3,1,NN,1,个,TRUE,TRUE,自动循环显示屏数
4,0,3,2,NN,1,秒,TRUE,TRUE,每屏显示时间
4,0,3,3,NN,1,位,TRUE,TRUE,显示电能小数位数
4,0,3,4,NN,1,位,TRUE,TRUE,显示功率(最大需量)小数位数
4,0,3,5,NN,1,个,TRUE,TRUE,按键循环显示屏数
4,0,3,6,NNNNNN,3,,TRUE,TRUE,电流互感器变比
4,0,3,7,NNNNNN,3,,TRUE,TRUE,电压互感器变比
4,0,4,1,NNNNNNNNNNNN,6,,TRUE,TRUE,通信地址
4,0,4,2,NNNNNNNNNNNN,6,,TRUE,TRUE,表号
4,0,4,3,NN...NN,32,,TRUE,TRUE,资产管理编码(ASCII码)
4,0,4,4,XXXXXXXXXXXX,6,,TRUE,FALSE,额定电压(ASCII码)
4,0,4,5,XXXXXXXXXXXX,6,,TRUE,FALSE,额定电流/基本电流(ASCII码)
4,0,4,6,XXXXXXXXXXXX,6,,TRUE,FALSE,最大电流(ASCII码)
4,0,4,7,XXXXXXXX,4,,TRUE,FALSE,有功准确度等级(ASCII码)
4,0,4,8,XXXXXXXX,4,,TRUE,FALSE,无功准确度等级(ASCII码)
4,0,4,9,XXXXXX,3,imp/kWh,TRUE,FALSE,电表有功常数
4,0,4,0A,XXXXXX,3,imp/kvarh,TRUE,FALSE,电表无功常数
4,0,4,0B,XX...XX,10,,TRUE,FALSE,电表型号(ASCII码)
4,0,4,0C,XX...XX,10,,TRUE,FALSE,生产日期(ASCII码)
4,0,4,0D,XX...XX,16,,TRUE,FALSE,协议版本号(ASCII码)
4,0,4,0E,NNNNNNNNNNNN,6,,TRUE,FALSE,客户编号
4,0,5,1,XXXX,2,,TRUE,FALSE,电表运行状态字1
4,0,6,1,NN,1,,TRUE,TRUE,有功组合方式特征字
4,0,6,2,NN,1,,TRUE,TRUE,无功组合方式1特征字
4,0,6,3,NN,1,,TRUE,TRUE,无功组合方式2特征字
4,0,7,1,NN,1,,TRUE,TRUE,调制型红外光口通信速率特征字
4,0,7,2,NN,1,,TRUE,TRUE,接触式红外光口通信速率特征字
4,0,7,3,NN,1,,TRUE,TRUE,通信口1通信速率特征字
4,0,7,4,NN,1,,TRUE,TRUE,通信口2通信速率特征字
4,0,7,5,NN,1,,TRUE,TRUE,通信口3通信速率特征字
4,0,8,1,NN,1,,TRUE,TRUE,周休日特征字
4,0,8,2,NN,1,,TRUE,TRUE,周休日采用的日时段表号
4,0,9,1,NN,1,,TRUE,TRUE,负荷记录模式字
4,0,9,2,NN,1,,TRUE,TRUE,定时冻结数据模式字
4,0,9,3,NN,1,,TRUE,TRUE,瞬时冻结数据模式字
4,0,9,4,NN,1,,TRUE,TRUE,约定冻结数据模式字
4,0,9,5,NN,1,,TRUE,TRUE,整点冻结数据模式字
4,0,9,6,NN,1,,TRUE,TRUE,日冻结数据模式字
4,0,A,1,MMDDhhmm,4,,TRUE,TRUE,负荷记录起始时间
4,0,B,1,DDhh,2,,TRUE,TRUE,每月第1结算日
4,0,B,2,DDhh,2,,TRUE,TRUE,每月第2结算日
4,0,B,3,DDhh,2,,TRUE,TRUE,每月第3结算日
4,0,C,1,NNNNNNNN,4,,FALSE,TRUE,0级密码
4,0,D,1,N.NNN,2,,TRUE,TRUE,A相电导系数
4,0,D,2,N.NNN,2,,TRUE,TRUE,A相电纳系数
4,0,D,3,N.NNN,2,,TRUE,TRUE,A相电阻系数
4,0,D,4,N.NNN,2,,TRUE,TRUE,A相电抗系数
4,0,D,5,N.NNN,2,,TRUE,TRUE,B相电导系数
4,0,D,6,N.NNN,2,,TRUE,TRUE,B相电纳系数
4,0,D,7,N.NNN,2,,TRUE,TRUE,B相电阻系数
4,0,D,8,N.NNN,2,,TRUE,TRUE,B相电抗系数
4,0,D,9,N.NNN,2,,TRUE,TRUE,C相电导系数
4,0,D,0A,N.NNN,2,,TRUE,TRUE,C相电纳系数
4,0,D,0B,N.NNN,2,,TRUE,TRUE,C相电阻系数
4,0,D,0C,N.NNN,2,,TRUE,TRUE,C相电抗系数
4,0,E,1,NN.NNNN,3,kW,TRUE,TRUE,正向有功功率上限值
4,0,E,2,NN.NNNN,3,kW,TRUE,TRUE,反向有功功率上限值
4,0,E,3,NNN.N,2,V,TRUE,TRUE,电压上限值
4,0,E,4,NNN.N,2,V,TRUE,TRUE,电压下限值
4,0,F,1,XXXXXX.XX,4,kWh,TRUE,TRUE,报警电量1限值
4,0,F,2,XXXXXX.XX,4,kWh,TRUE,TRUE,报警电量2限值
4,0,F,3,XXXXXX.XX,4,kWh,TRUE,TRUE,囤积电量限值
4,0,F,4,XXXXXX.XX,4,kWh,TRUE,TRUE,透支电量限值
4,0,10,1,XXXXXX.XX,4,元,TRUE,TRUE,报警金额1限值
4,0,10,2,XXXXXX.XX,4,元,TRUE,TRUE,报警金额2限值
4,0,10,3,XXXXXX.XX,4,元,TRUE,TRUE,透支金额限值
4,0,10,4,NNNNNN.NN,4,元,TRUE,TRUE,囤积金额限值
4,0,10,5,NNNNNN.NN,4,元,TRUE,TRUE,合闸允许金额限值
4,0,12,1,YYMMDDhhmm,5,年月日时分,TRUE,TRUE,整点冻结起始时间
4,0,12,2,NN,1,分钟,TRUE,TRUE,整点冻结时间间隔
4,0,12,3,hhmm,2,时分,TRUE,TRUE,日冻结时间
4,0,13,1,NN,1,,TRUE,TRUE,无线通信在线及信号强弱指示
4,0,14,1,NNNN,2,,TRUE,TRUE,跳闸延时时间NNNN为跳闸前告警时间
4,80,0,1,NN...NN,32,,TRUE,FALSE,厂家软件版本号(ASCII码)
4,80,0,2,NN...NN,33,,TRUE,FALSE,厂家硬件版本号(ASCII码)
4,80,0,3,NN...NN,34,,TRUE,FALSE,厂家编号(ASCII码)
4,9,1,1,NNN.N,2,V,TRUE,TRUE,失压事件电压触发上限
4,9,1,2,NNN.N,2,V,TRUE,TRUE,失压事件电压恢复下限
4,9,1,3,NN.NNNN,3,A,TRUE,TRUE,失压事件电流触发下限
4,9,1,4,NN,1,秒,TRUE,TRUE,失压事件判定延时时间
4,9,2,1,NNN.N,2,V,TRUE,TRUE,欠压事件电压触发上限
4,9,2,2,NN,1,秒,TRUE,TRUE,欠压事件判定延时时间
4,9,3,1,NNN.N,2,V,TRUE,TRUE,过压事件电压触发下限
4,9,3,2,NN,1,秒,TRUE,TRUE,过压事件判定延时时间
1 di3 di2 di1 di0 format length unit read write name
2 0 0 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)组合有功总电能
3 0 1 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)正向有功总电能
4 0 2 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)反向有功总电能
5 0 3 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)组合无功1总电能
6 0 4 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)组合无功2总电能
7 0 5 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)第一象限无功总电能
8 0 6 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)第二象限无功总电能
9 0 7 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)第三象限无功总电能
10 0 8 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)第四象限无功总电能
11 0 9 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)正向视在总电能
12 0 A 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)反向视在总电能
13 0 B 0 0 XXXXXX.XX 4 kWh TRUE FALSE 当前结算周期组合有功总累计用电量
14 0 80 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)关联总电能
15 0 81 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)正向有功基波总电能
16 0 82 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)反向有功基波总电能
17 0 83 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)正向有功谐波总电能
18 0 84 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)反向有功谐波总电能
19 0 85 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)铜损有功总电能补偿量
20 0 86 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)铁损有功总电能补偿量
21 0 15 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)A相正向有功电能
22 0 16 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)A相反向有功电能
23 0 17 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)A相组合无功1电能
24 0 18 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)A相组合无功2电能
25 0 19 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)A相第一象限无功电能
26 0 1A 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)A相第二象限无功电能
27 0 1B 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)A相第三象限无功电能
28 0 1C 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)A相第四象限无功电能
29 0 1D 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)A相正向视在电能
30 0 1E 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)A相反向视在电能
31 0 29 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)B相正向有功电能
32 0 2A 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)B相反向有功电能
33 0 2B 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)B相组合无功1电能
34 0 2C 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)B相组合无功2电能
35 0 2D 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)B相第一象限无功电能
36 0 2E 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)B相第二象限无功电能
37 0 2F 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)B相第三象限无功电能
38 0 30 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)B相第四象限无功电能
39 0 31 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)B相正向视在电能
40 0 32 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)B相反向视在电能
41 0 3D 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)C相正向有功电能
42 0 3E 0 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)C相反向有功电能
43 0 3F 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)C相组合无功1电能
44 0 40 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)C相组合无功2电能
45 0 41 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)C相第一象限无功电能
46 0 42 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)C相第二象限无功电能
47 0 43 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)C相第三象限无功电能
48 0 44 0 0 XXXXXX.XX 4 kvarh TRUE FALSE (当前)C相第四象限无功电能
49 0 45 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)C相正向视在电能
50 0 46 0 0 XXXXXX.XX 4 kVAh TRUE FALSE (当前)C相反向视在电能
51 0 0 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)组合有功总电能
52 0 1 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)正向有功总电能
53 0 2 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)反向有功总电能
54 0 3 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)组合无功1总电能
55 0 4 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)组合无功2总电能
56 0 5 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)第一象限无功总电能
57 0 6 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)第二象限无功总电能
58 0 7 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)第三象限无功总电能
59 0 8 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)第四象限无功总电能
60 0 9 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)正向视在总电能
61 0 A 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)反向视在总电能
62 0 80 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)关联总电能
63 0 81 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)正向有功基波总电能
64 0 82 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)反向有功基波总电能
65 0 83 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)正向有功谐波总电能
66 0 84 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)反向有功谐波总电能
67 0 85 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)铜损有功总电能补偿量
68 0 86 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)铁损有功总电能补偿量
69 0 15 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)A相正向有功电能
70 0 16 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)A相反向有功电能
71 0 17 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)A相组合无功1电能
72 0 18 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)A相组合无功2电能
73 0 19 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)A相第一象限无功电能
74 0 1A 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)A相第二象限无功电能
75 0 1B 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)A相第三象限无功电能
76 0 1C 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)A相第四象限无功电能
77 0 1D 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)A相正向视在电能
78 0 1E 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)A相反向视在电能
79 0 29 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)B相正向有功电能
80 0 2A 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)B相反向有功电能
81 0 2B 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)B相组合无功1电能
82 0 2C 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)B相组合无功2电能
83 0 2D 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)B相第一象限无功电能
84 0 2E 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)B相第二象限无功电能
85 0 2F 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)B相第三象限无功电能
86 0 30 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)B相第四象限无功电能
87 0 31 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)B相正向视在电能
88 0 32 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)B相反向视在电能
89 0 3D 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)C相正向有功电能
90 0 3E 0 1 XXXXXX.XX 4 kWh TRUE FALSE (上1结算日)C相反向有功电能
91 0 3F 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)C相组合无功1电能
92 0 40 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)C相组合无功2电能
93 0 41 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)C相第一象限无功电能
94 0 42 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)C相第二象限无功电能
95 0 43 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)C相第三象限无功电能
96 0 44 0 1 XXXXXX.XX 4 kvarh TRUE FALSE (上1结算日)C相第四象限无功电能
97 0 45 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)C相正向视在电能
98 0 46 0 1 XXXXXX.XX 4 kVAh TRUE FALSE (上1结算日)C相反向视在电能
99 0 0 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)组合有功总电能
100 0 1 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)正向有功总电能
101 0 2 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)反向有功总电能
102 0 3 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)组合无功1总电能
103 0 4 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)组合无功2总电能
104 0 5 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)第一象限无功总电能
105 0 6 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)第二象限无功总电能
106 0 7 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)第三象限无功总电能
107 0 8 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)第四象限无功总电能
108 0 9 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)正向视在总电能
109 0 A 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)反向视在总电能
110 0 80 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)关联总电能
111 0 81 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)正向有功基波总电能
112 0 82 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)反向有功基波总电能
113 0 83 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)正向有功谐波总电能
114 0 84 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)反向有功谐波总电能
115 0 85 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)铜损有功总电能补偿量
116 0 86 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)铁损有功总电能补偿量
117 0 15 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相正向有功电能
118 0 16 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相反向有功电能
119 0 17 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)A相组合无功1电能
120 0 18 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)A相组合无功2电能
121 0 19 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)A相第一象限无功电能
122 0 1A 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)A相第二象限无功电能
123 0 1B 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)A相第三象限无功电能
124 0 1C 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)A相第四象限无功电能
125 0 1D 0 C XXXXXX.XX 4 kVAh TRUE FALSE (上12结算日)A相正向视在电能
126 0 1E 0 C XXXXXX.XX 4 kVAh TRUE FALSE (上12结算日)A相反向视在电能
127 0 9 1 0 XXXXXX.XX 4 kWh TRUE FALSE (当前)剩余电量
128 0 9 1 1 XXXXXX.XX 4 kWh TRUE FALSE (当前)透支电量
129 0 9 2 0 XXXXXX.XX 4 TRUE FALSE (当前)剩余金额
130 0 9 2 1 XXXXXX.XX 4 TRUE FALSE (当前)透支金额
131 0 94 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相关联电能
132 0 95 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相正向有功基波电能
133 0 96 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相反向有功基波电能
134 0 97 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相正向有功谐波电能
135 0 98 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相反向有功谐波电能
136 0 99 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相铜损有功电能补偿量
137 0 9A 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)A相铁损有功电能补偿量
138 0 29 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)B相正向有功电能
139 0 2A 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)B相反向有功电能
140 0 2B 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)B相组合无功1电能
141 0 2C 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)B相组合无功2电能
142 0 2D 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)B相第一象限无功电能
143 0 2E 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)B相第二象限无功电能
144 0 2F 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)B相第三象限无功电能
145 0 30 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)B相第四象限无功电能
146 0 31 0 C XXXXXX.XX 4 kVAh TRUE FALSE (上12结算日)B相正向视在电能
147 0 32 0 C XXXXXX.XX 4 kVAh TRUE FALSE (上12结算日)B相反向视在电能
148 0 3D 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)C相正向有功电能
149 0 3E 0 C XXXXXX.XX 4 kWh TRUE FALSE (上12结算日)C相反向有功电能
150 0 3F 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)C相组合无功1电能
151 0 40 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)C相组合无功2电能
152 0 41 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)C相第一象限无功电能
153 0 42 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)C相第二象限无功电能
154 0 43 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)C相第三象限无功电能
155 0 44 0 C XXXXXX.XX 4 kvarh TRUE FALSE (上12结算日)C相第四象限无功电能
156 0 45 0 C XXXXXX.XX 4 kVAh TRUE FALSE (上12结算日)C相正向视在电能
157 0 46 0 C XXXXXX.XX 4 kVAh TRUE FALSE (上12结算日)C相反向视在电能
158 1 1 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)正向有功总最大需量及发生时间
159 1 2 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)反向有功总最大需量及发生时间
160 1 3 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)组合无功1总最大需量及发生时间
161 1 4 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)组合无功2总最大需量及发生时间
162 1 5 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)第一象限无功总最大需量及发生时间
163 1 6 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)第二象限无功总最大需量及发生时间
164 1 7 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)第三象限无功总最大需量及发生时间
165 1 8 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)第四象限无功总最大需量及发生时间
166 1 9 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)正向视在总最大需量及发生时间
167 1 A 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)反向视在总最大需量及发生时间
168 1 15 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)A相正向有功最大需量及发生时间
169 1 16 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)A相反向有功最大需量及发生时间
170 1 17 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)A相组合无功1最大需量及发生时间
171 1 18 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)A相组合无功2最大需量及发生时间
172 1 19 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)A相第一象限无功最大需量及发生时间
173 1 1A 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)A相第二象限无功最大需量及发生时间
174 1 1B 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)A相第三象限无功最大需量及发生时间
175 1 1C 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)A相第四象限无功最大需量及发生时间
176 1 1D 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)A相正向视在最大需量及发生时间
177 1 1E 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)A相反向视在最大需量及发生时间
178 1 29 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)B相正向有功最大需量及发生时间
179 1 2A 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)B相反向有功最大需量及发生时间
180 1 2B 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)B相组合无功1最大需量及发生时间
181 1 2C 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)B相组合无功2最大需量及发生时间
182 1 2D 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)B相第一象限无功最大需量及发生时间
183 1 2E 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)B相第二象限无功最大需量及发生时间
184 1 2F 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)B相第三象限无功最大需量及发生时间
185 1 30 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)B相第四象限无功最大需量及发生时间
186 1 31 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)B相正向视在最大需量及发生时间
187 1 32 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)B相反向视在最大需量及发生时间
188 1 3D 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)C相正向有功最大需量及发生时间
189 1 3E 0 0 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (当前)C相反向有功最大需量及发生时间
190 1 3F 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)C相组合无功1最大需量及发生时间
191 1 40 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)C相组合无功2最大需量及发生时间
192 1 41 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)C相第一象限无功最大需量及发生时间
193 1 42 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)C相第二象限无功最大需量及发生时间
194 1 43 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)C相第三象限无功最大需量及发生时间
195 1 44 0 0 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (当前)C相第四象限无功最大需量及发生时间
196 1 45 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)C相正向视在最大需量及发生时间
197 1 46 0 0 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (当前)C相反向视在最大需量及发生时间
198 1 1 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)正向有功总最大需量及发生时间
199 1 2 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)反向有功总最大需量及发生时间
200 1 3 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)组合无功1总最大需量及发生时间
201 1 4 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)组合无功2总最大需量及发生时间
202 1 5 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)第一象限无功总最大需量及发生时间
203 1 6 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)第二象限无功总最大需量及发生时间
204 1 7 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)第三象限无功总最大需量及发生时间
205 1 8 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)第四象限无功总最大需量及发生时间
206 1 9 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)正向视在总最大需量及发生时间
207 1 A 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)反向视在总最大需量及发生时间
208 1 15 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)A相正向有功最大需量及发生时间
209 1 16 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)A相反向有功最大需量及发生时间
210 1 17 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)A相组合无功1最大需量及发生时间
211 1 18 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)A相组合无功2最大需量及发生时间
212 1 19 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)A相第一象限无功最大需量及发生时间
213 1 1A 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)A相第二象限无功最大需量及发生时间
214 1 1B 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)A相第三象限无功最大需量及发生时间
215 1 1C 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)A相第四象限无功最大需量及发生时间
216 1 1D 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)A相正向视在最大需量及发生时间
217 1 1E 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)A相反向视在最大需量及发生时间
218 1 29 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)B相正向有功最大需量及发生时间
219 1 2A 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)B相反向有功最大需量及发生时间
220 1 2B 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)B相组合无功1最大需量及发生时间
221 1 2C 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)B相组合无功2最大需量及发生时间
222 1 2D 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)B相第一象限无功最大需量及发生时间
223 1 2E 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)B相第二象限无功最大需量及发生时间
224 1 2F 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)B相第三象限无功最大需量及发生时间
225 1 30 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)B相第四象限无功最大需量及发生时间
226 1 31 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)B相正向视在最大需量及发生时间
227 1 32 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)B相反向视在最大需量及发生时间
228 1 3D 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)C相正向有功最大需量及发生时间
229 1 3E 0 1 XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上1结算日)C相反向有功最大需量及发生时间
230 1 3F 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)C相组合无功1最大需量及发生时间
231 1 40 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)C相组合无功2最大需量及发生时间
232 1 41 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)C相第一象限无功最大需量及发生时间
233 1 42 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)C相第二象限无功最大需量及发生时间
234 1 43 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)C相第三象限无功最大需量及发生时间
235 1 44 0 1 XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上1结算日)C相第四象限无功最大需量及发生时间
236 1 45 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)C相正向视在最大需量及发生时间
237 1 46 0 1 XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上1结算日)C相反向视在最大需量及发生时间
238 1 1 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)正向有功总最大需量及发生时间
239 1 2 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)反向有功总最大需量及发生时间
240 1 3 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)组合无功1总最大需量及发生时间
241 1 4 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)组合无功2总最大需量及发生时间
242 1 5 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)第一象限无功总最大需量及发生时间
243 1 6 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)第二象限无功总最大需量及发生时间
244 1 7 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)第三象限无功总最大需量及发生时间
245 1 8 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)第四象限无功总最大需量及发生时间
246 1 9 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)正向视在总最大需量及发生时间
247 1 A 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)反向视在总最大需量及发生时间
248 1 15 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)A相正向有功最大需量及发生时间
249 1 16 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)A相反向有功最大需量及发生时间
250 1 17 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)A相组合无功1最大需量及发生时间
251 1 18 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)A相组合无功2最大需量及发生时间
252 1 19 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)A相第一象限无功最大需量及发生时间
253 1 1A 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)A相第二象限无功最大需量及发生时间
254 1 1B 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)A相第三象限无功最大需量及发生时间
255 1 1C 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)A相第四象限无功最大需量及发生时间
256 1 1D 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)A相正向视在最大需量及发生时间
257 1 1E 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)A相反向视在最大需量及发生时间
258 1 29 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)B相正向有功最大需量及发生时间
259 1 2A 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)B相反向有功最大需量及发生时间
260 1 2B 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)B相组合无功1最大需量及发生时间
261 1 2C 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)B相组合无功2最大需量及发生时间
262 1 2D 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)B相第一象限无功最大需量及发生时间
263 1 2E 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)B相第二象限无功最大需量及发生时间
264 1 2F 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)B相第三象限无功最大需量及发生时间
265 1 30 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)B相第四象限无功最大需量及发生时间
266 1 31 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)B相正向视在最大需量及发生时间
267 1 32 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)B相反向视在最大需量及发生时间
268 1 3D 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)C相正向有功最大需量及发生时间
269 1 3E 0 C XX.XXXX|YYMMDDhhmm 8 kW TRUE FALSE (上12结算日)C相反向有功最大需量及发生时间
270 1 3F 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)C相组合无功1最大需量及发生时间
271 1 40 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)C相组合无功2最大需量及发生时间
272 1 41 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)C相第一象限无功最大需量及发生时间
273 1 42 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)C相第二象限无功最大需量及发生时间
274 1 43 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)C相第三象限无功最大需量及发生时间
275 1 44 0 C XX.XXXX|YYMMDDhhmm 8 kvar TRUE FALSE (上12结算日)C相第四象限无功最大需量及发生时间
276 1 45 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)C相正向视在最大需量及发生时间
277 1 46 0 C XX.XXXX|YYMMDDhhmm 8 kVA TRUE FALSE (上12结算日)C相反向视在最大需量及发生时间
278 2 1 1 0 XXX.X 2 V TRUE FALSE A相电压
279 2 1 2 0 XXX.X 2 V TRUE FALSE B相电压
280 2 1 3 0 XXX.X 2 V TRUE FALSE C相电压
281 2 2 1 0 XXX.XXX 3 A TRUE FALSE A相电流
282 2 2 2 0 XXX.XXX 3 A TRUE FALSE B相电流
283 2 2 3 0 XXX.XXX 3 A TRUE FALSE C相电流
284 2 3 0 0 XXX.XXX 3 kW TRUE FALSE 瞬时总有功功率
285 2 3 1 0 XXX.XXX 3 kW TRUE FALSE 瞬时A相有功功率
286 2 3 2 0 XXX.XXX 3 kW TRUE FALSE 瞬时B相有功功率
287 2 3 3 0 XXX.XXX 3 kW TRUE FALSE 瞬时C相有功功率
288 2 4 0 0 XXX.XXX 3 kvar TRUE FALSE 瞬时总无功功率
289 2 4 1 0 XXX.XXX 3 kvar TRUE FALSE 瞬时A相无功功率
290 2 4 2 0 XXX.XXX 3 kvar TRUE FALSE 瞬时B相无功功率
291 2 4 3 0 XXX.XXX 3 kvar TRUE FALSE 瞬时C相无功功率
292 2 5 0 0 XXX.XXX 3 kVA TRUE FALSE 瞬时总视在功率
293 2 5 1 0 XXX.XXX 3 kVA TRUE FALSE 瞬时A相视在功率
294 2 5 2 0 XXX.XXX 3 kVA TRUE FALSE 瞬时B相视在功率
295 2 5 3 0 XXX.XXX 3 kVA TRUE FALSE 瞬时C相视在功率
296 2 6 0 0 X.XXX 2 kVA TRUE FALSE 总功率因数
297 2 6 1 0 X.XXX 2 kVA TRUE FALSE A相功率因数
298 2 6 2 0 X.XXX 2 kVA TRUE FALSE B相功率因数
299 2 6 3 0 X.XXX 2 kVA TRUE FALSE C相功率因数
300 2 7 1 0 X.XXX 2 ° TRUE FALSE A相相角
301 2 7 2 0 X.XXX 2 ° TRUE FALSE B相相角
302 2 7 3 0 X.XXX 2 ° TRUE FALSE C相相角
303 2 8 1 0 X.XXX 2 % TRUE FALSE A相电压波形失真度
304 2 8 2 0 X.XXX 2 % TRUE FALSE B相电压波形失真度
305 2 8 3 0 X.XXX 2 % TRUE FALSE C相电压波形失真度
306 2 9 1 0 X.XXX 2 % TRUE FALSE A相电流波形失真度
307 2 9 2 0 X.XXX 2 % TRUE FALSE B相电流波形失真度
308 2 9 3 0 X.XXX 2 % TRUE FALSE C相电流波形失真度
309 2 A 1 0 X.XXX 2 % TRUE FALSE A相电压1次谐波含量
310 2 A 2 0 X.XXX 2 % TRUE FALSE B相电压1次谐波含量
311 2 A 3 0 X.XXX 2 % TRUE FALSE C相电压1次谐波含量
312 2 B 1 0 X.XXX 2 % TRUE FALSE A相电流1次谐波含量
313 2 B 2 0 X.XXX 2 % TRUE FALSE B相电流1次谐波含量
314 2 B 3 0 X.XXX 2 % TRUE FALSE C相电流1次谐波含量
315 2 80 0 1 XXX.XXX 3 A TRUE FALSE 零线电流
316 2 80 0 2 XX.XX 2 Hz TRUE FALSE 电网频率
317 2 80 0 3 XX.XXXX 3 kW TRUE FALSE 一分钟有功总平均功率
318 2 80 0 4 XX.XXXX 3 kW TRUE FALSE 当前有功需量
319 2 80 0 5 XX.XXXX 3 kvar TRUE FALSE 当前无功需量
320 2 80 0 6 XX.XXXX 3 kVA TRUE FALSE 当前视在需量
321 2 80 0 7 XXX.X 2 TRUE FALSE 表内温度
322 2 80 0 8 XX.XX 2 V TRUE FALSE 时钟电池电压(内部)
323 2 80 0 9 XX.XX 2 V TRUE FALSE 停电抄表电池电压(外部)
324 2 80 0 0A XXXXXXXX 4 TRUE FALSE 内部电池工作时间
325 2 80 0 0B XXXX.XXXX 4 元/kWh TRUE FALSE 当前阶梯电价
326 4 0 1 1 YYMMDDWW 4 年月日星期 TRUE TRUE 日期及星期(其中0代表星期天)
327 4 0 1 2 hhmmss 3 时分秒 TRUE TRUE 时间
328 4 0 1 3 NN 1 TRUE TRUE 最大需量周期
329 4 0 1 4 NN 1 TRUE TRUE 滑差时间
330 4 0 1 5 XXXX 2 毫秒 TRUE TRUE 校表脉冲宽度
331 4 0 1 6 YYMMDDhhmm 5 年月日时分 TRUE TRUE 两套时区表切换时间
332 4 0 1 7 YYMMDDhhmm 5 年月日时分 TRUE TRUE 两套日时段表切换时间
333 4 0 1 8 YYMMDDhhmm 5 年月日时分 TRUE TRUE 两套费率电价切换时间
334 4 0 1 9 YYMMDDhhmm 5 年月日时分 TRUE TRUE 两套阶梯切换时间
335 4 0 2 1 NN 1 TRUE TRUE 年时区数p≤14
336 4 0 2 2 NN 1 TRUE TRUE 日时段表数q≤8
337 4 0 2 3 NN 1 TRUE TRUE 日时段数(每日切换数)m≤14
338 4 0 2 4 NN 1 TRUE TRUE 费率数k≤63
339 4 0 2 5 NNNN 2 TRUE TRUE 公共假日数n≤254
340 4 0 2 6 NN 1 TRUE TRUE 谐波分析次数
341 4 0 2 7 NN 1 TRUE TRUE 阶梯数
342 4 0 3 1 NN 1 TRUE TRUE 自动循环显示屏数
343 4 0 3 2 NN 1 TRUE TRUE 每屏显示时间
344 4 0 3 3 NN 1 TRUE TRUE 显示电能小数位数
345 4 0 3 4 NN 1 TRUE TRUE 显示功率(最大需量)小数位数
346 4 0 3 5 NN 1 TRUE TRUE 按键循环显示屏数
347 4 0 3 6 NNNNNN 3 TRUE TRUE 电流互感器变比
348 4 0 3 7 NNNNNN 3 TRUE TRUE 电压互感器变比
349 4 0 4 1 NNNNNNNNNNNN 6 TRUE TRUE 通信地址
350 4 0 4 2 NNNNNNNNNNNN 6 TRUE TRUE 表号
351 4 0 4 3 NN...NN 32 TRUE TRUE 资产管理编码(ASCII码)
352 4 0 4 4 XXXXXXXXXXXX 6 TRUE FALSE 额定电压(ASCII码)
353 4 0 4 5 XXXXXXXXXXXX 6 TRUE FALSE 额定电流/基本电流(ASCII码)
354 4 0 4 6 XXXXXXXXXXXX 6 TRUE FALSE 最大电流(ASCII码)
355 4 0 4 7 XXXXXXXX 4 TRUE FALSE 有功准确度等级(ASCII码)
356 4 0 4 8 XXXXXXXX 4 TRUE FALSE 无功准确度等级(ASCII码)
357 4 0 4 9 XXXXXX 3 imp/kWh TRUE FALSE 电表有功常数
358 4 0 4 0A XXXXXX 3 imp/kvarh TRUE FALSE 电表无功常数
359 4 0 4 0B XX...XX 10 TRUE FALSE 电表型号(ASCII码)
360 4 0 4 0C XX...XX 10 TRUE FALSE 生产日期(ASCII码)
361 4 0 4 0D XX...XX 16 TRUE FALSE 协议版本号(ASCII码)
362 4 0 4 0E NNNNNNNNNNNN 6 TRUE FALSE 客户编号
363 4 0 5 1 XXXX 2 TRUE FALSE 电表运行状态字1
364 4 0 6 1 NN 1 TRUE TRUE 有功组合方式特征字
365 4 0 6 2 NN 1 TRUE TRUE 无功组合方式1特征字
366 4 0 6 3 NN 1 TRUE TRUE 无功组合方式2特征字
367 4 0 7 1 NN 1 TRUE TRUE 调制型红外光口通信速率特征字
368 4 0 7 2 NN 1 TRUE TRUE 接触式红外光口通信速率特征字
369 4 0 7 3 NN 1 TRUE TRUE 通信口1通信速率特征字
370 4 0 7 4 NN 1 TRUE TRUE 通信口2通信速率特征字
371 4 0 7 5 NN 1 TRUE TRUE 通信口3通信速率特征字
372 4 0 8 1 NN 1 TRUE TRUE 周休日特征字
373 4 0 8 2 NN 1 TRUE TRUE 周休日采用的日时段表号
374 4 0 9 1 NN 1 TRUE TRUE 负荷记录模式字
375 4 0 9 2 NN 1 TRUE TRUE 定时冻结数据模式字
376 4 0 9 3 NN 1 TRUE TRUE 瞬时冻结数据模式字
377 4 0 9 4 NN 1 TRUE TRUE 约定冻结数据模式字
378 4 0 9 5 NN 1 TRUE TRUE 整点冻结数据模式字
379 4 0 9 6 NN 1 TRUE TRUE 日冻结数据模式字
380 4 0 A 1 MMDDhhmm 4 TRUE TRUE 负荷记录起始时间
381 4 0 B 1 DDhh 2 TRUE TRUE 每月第1结算日
382 4 0 B 2 DDhh 2 TRUE TRUE 每月第2结算日
383 4 0 B 3 DDhh 2 TRUE TRUE 每月第3结算日
384 4 0 C 1 NNNNNNNN 4 FALSE TRUE 0级密码
385 4 0 D 1 N.NNN 2 TRUE TRUE A相电导系数
386 4 0 D 2 N.NNN 2 TRUE TRUE A相电纳系数
387 4 0 D 3 N.NNN 2 TRUE TRUE A相电阻系数
388 4 0 D 4 N.NNN 2 TRUE TRUE A相电抗系数
389 4 0 D 5 N.NNN 2 TRUE TRUE B相电导系数
390 4 0 D 6 N.NNN 2 TRUE TRUE B相电纳系数
391 4 0 D 7 N.NNN 2 TRUE TRUE B相电阻系数
392 4 0 D 8 N.NNN 2 TRUE TRUE B相电抗系数
393 4 0 D 9 N.NNN 2 TRUE TRUE C相电导系数
394 4 0 D 0A N.NNN 2 TRUE TRUE C相电纳系数
395 4 0 D 0B N.NNN 2 TRUE TRUE C相电阻系数
396 4 0 D 0C N.NNN 2 TRUE TRUE C相电抗系数
397 4 0 E 1 NN.NNNN 3 kW TRUE TRUE 正向有功功率上限值
398 4 0 E 2 NN.NNNN 3 kW TRUE TRUE 反向有功功率上限值
399 4 0 E 3 NNN.N 2 V TRUE TRUE 电压上限值
400 4 0 E 4 NNN.N 2 V TRUE TRUE 电压下限值
401 4 0 F 1 XXXXXX.XX 4 kWh TRUE TRUE 报警电量1限值
402 4 0 F 2 XXXXXX.XX 4 kWh TRUE TRUE 报警电量2限值
403 4 0 F 3 XXXXXX.XX 4 kWh TRUE TRUE 囤积电量限值
404 4 0 F 4 XXXXXX.XX 4 kWh TRUE TRUE 透支电量限值
405 4 0 10 1 XXXXXX.XX 4 TRUE TRUE 报警金额1限值
406 4 0 10 2 XXXXXX.XX 4 TRUE TRUE 报警金额2限值
407 4 0 10 3 XXXXXX.XX 4 TRUE TRUE 透支金额限值
408 4 0 10 4 NNNNNN.NN 4 TRUE TRUE 囤积金额限值
409 4 0 10 5 NNNNNN.NN 4 TRUE TRUE 合闸允许金额限值
410 4 0 12 1 YYMMDDhhmm 5 年月日时分 TRUE TRUE 整点冻结起始时间
411 4 0 12 2 NN 1 分钟 TRUE TRUE 整点冻结时间间隔
412 4 0 12 3 hhmm 2 时分 TRUE TRUE 日冻结时间
413 4 0 13 1 NN 1 TRUE TRUE 无线通信在线及信号强弱指示
414 4 0 14 1 NNNN 2 TRUE TRUE 跳闸延时时间(NNNN为跳闸前告警时间)
415 4 80 0 1 NN...NN 32 TRUE FALSE 厂家软件版本号(ASCII码)
416 4 80 0 2 NN...NN 33 TRUE FALSE 厂家硬件版本号(ASCII码)
417 4 80 0 3 NN...NN 34 TRUE FALSE 厂家编号(ASCII码)
418 4 9 1 1 NNN.N 2 V TRUE TRUE 失压事件电压触发上限
419 4 9 1 2 NNN.N 2 V TRUE TRUE 失压事件电压恢复下限
420 4 9 1 3 NN.NNNN 3 A TRUE TRUE 失压事件电流触发下限
421 4 9 1 4 NN 1 TRUE TRUE 失压事件判定延时时间
422 4 9 2 1 NNN.N 2 V TRUE TRUE 欠压事件电压触发上限
423 4 9 2 2 NN 1 TRUE TRUE 欠压事件判定延时时间
424 4 9 3 1 NNN.N 2 V TRUE TRUE 过压事件电压触发下限
425 4 9 3 2 NN 1 TRUE TRUE 过压事件判定延时时间

@ -0,0 +1,8 @@
plugin:
runMode: prod
mainPackage: cc.iotkit.plugin
tcp:
host: 25on621889.goho.co
port: 43161
interval: 10000

@ -0,0 +1,23 @@
[
{
"id": "host",
"name": "服务端ip",
"type": "text",
"value": "25on621889.goho.co",
"desc": "服务端ip"
},
{
"id": "port",
"name": "服务端端口",
"type": "number",
"value": 43161,
"desc": "服务端端口"
},
{
"id": "interval",
"name": "采集频率",
"type": "number",
"value": 10000,
"desc": "采集频率"
}
]

@ -0,0 +1,36 @@
# iot-iita-plugins
#### Description
官方插件示例仓库
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

@ -1,153 +1,37 @@
<p align="center">
<img alt="logo" src="https://www.openiita.com/_nuxt/logo.Dn3SJdkk.png">
</p>
<p align="center"><img src="https://img.shields.io/badge/JDK-11+-important.svg"> <img src="https://img.shields.io/badge/Redis-5.0-important.svg"> <img src="https://img.shields.io/badge/SpringBoot-2.6.2-blue.svg"> <img src="https://img.shields.io/badge/Elasticsearch-7.15.2-red.svg"> <img src="https://img.shields.io/badge/Satoken-1.30.0-yellow.svg">
</p>
<p align="center"><img src="https://img.shields.io/badge/License-Apache2.0-lightgrey.svg">
<img src="https://img.shields.io/badge/Copyright-禹霖科技/openiita-60569f.svg"> <img src="https://img.shields.io/badge/v0.5.2-60569f.svg">
<img src="https://img.shields.io/badge/物联网平台-f44e91.svg">
</p>
#### 禹霖科技物联网平台开源项目
管理员账号密码admin/admin123
# iot-iita-plugins
#### 介绍
此仓库为禹霖科技(openyl)物联网平台开源项目。
禹霖科技开源平台是一个开源的物联网基础开发平台,提供了物联网及相关业务开发的常见基础功能, 能帮助你快速搭建自己的物联网相关业务平台。
系统包含了品类、物模型、消息转换、插件管理mqtt插件、EMQX插件、http插件、tcp插件、modbus插件等、设备管理、设备分组、规则引擎、第三方平台接入、数据流转http/mqtt/kafka、数据可视化、告警中心等模块。
### 商业版本
分布式协议商业版,支持协议组件水平扩展.对于设备数量多的用户可以考虑采购此版本
#### 低配服务器万级设备接入能力
服务器配置: 1台2核2G(阿里云突发性能实例)跑主程序、1台2核4G(阿里云突发性能实例)跑TDengine和RocketMq
程序配置使用内置H2数据库和内置mqtt
设备接入情况接入4千台网关+8千台子设备其中4千台子设备每10秒上报一条数据
稳定运行后设备在线并正常上报和下发、CPU占用未超过50%、界面操作没有卡顿
#### 低学习门槛
1、零配置一键运行小白也会用
2、低环境要求不需要额外安装软件
3、平民级代码没有形式化的分层和过度封装简单易懂
#### 主流数据库支持
关系型数据库理论上支持所有实现了标准SQL的数据库 DB2、H2、HSQLDB、MariaDB、Microsoft SQL Server、MySQL、Oracle、Postgres
时序数据库elasticsearch、TDEngine
K-V数据库redis
#### 消息总线支持
Vertx event-bus内置、RocketMQ通过扩展模块可接入其它任意消息中间件。
#### 系统生态
**前端项目:**
[![OPENIITA/iita-iot-web-admin](https://gitee.com/open-iita/iita-iot-web-admin/widgets/widget_card.svg?colors=393222,ebdfc1,fffae5,d8ca9f,393222,a28b40)](https://gitee.com/open-iita/iita-iot-web-admin)
**演示地址:** [点击访问](https://open-iita.feishu.cn/wiki/Km67wBKNNiFMrMkldCJcxa6vnLe)
**系统截图**
![输入图片说明](doc/screenshot.png)
官方插件示例仓库
#### 软件架构
软件架构说明
本系统采用springboot、jpa、vertx、redis、H2、mysql、elasticsearch、TDEngine、RocketMq、sa-token等框架和第三方软件中间件采用模块化无侵入集成非常容易扩展和替换。
默认使用内置的redis、h2数据库、es时序数据库、vertx消息总线和oss以减小启动难度可实现 **零配置一键启动** 程序在第一次启动的时候会使用data/init目录的示例数据进行初始化。
**注:** es版本为7.15.2mysql版本为8.0+
##### H2数据库切换为mysql方法其它数据库同理
https://open-iita.feishu.cn/wiki/IhIGwZSnsiMaDdkT98Xcvtb4nGh
##### es切换为TDengine(版本3.x)方法
https://open-iita.feishu.cn/wiki/IhIGwZSnsiMaDdkT98Xcvtb4nGh
##### 消息总线切换为RocketMq方法
1、注释掉iot-starter/pom.xml中的 iot-message-event-bus并打开iot-message-rocketmq的注释
2、application.xml中打开rocketmq配置
#### Docker运行
docker 镜像版本里的比较旧了,请参考dockerfile自己构建
启动后访问 http://localhost:8082/
#### 源码运行步骤
在线文档:[知识库](https://open-iita.feishu.cn/wiki/Km67wBKNNiFMrMkldCJcxa6vnLe)
在配置好相应的jdk11环境后推荐使用idea拉取代码等待项目加载完成以后先在Terminal里执行mvn clean install然后在idea的maven操作框上点击下刷新
![1](doc/image-20230702173737805.png)
然后就点击启动,看到如下所示日志即表示启动成功:
![image-20230702174747305](doc/image-20230702174747305.png)
#### 后端常见问题以及处理方式
#### 1、数据初始化不成功
删除以下标红的几个文件以及文件夹后,重复下上面启动项目的操作:
![image-20230702173852342](doc/image-20230702173852342.png)
切换成外置中间件数据库时,也注意删除相关库以及数据。
##### 2、提示找不到QTbxxxxx类
在Terminal里执行mvn clean install再刷新下这个出现的原因是因为采用了querydsl插件打包时会生成相关的类有可能idea的扫描不及时导致未识别到
![111](doc/image-20230702173737805.png)
##### 3启动提示xxMapperxxx类bean重复
处理办法同2一样原因也类似采用了mapstruct plus插件打包时候会生成对应类的getset方法工具类可能idea的扫描不及时导致未识别到
#### 服务器部署
执行maven打包在iot-starter模块中将打包后的iot-starter.xx.zip上传到服务器
解压后先执行: java -jar lib/iot-start.xxx.jar确认成功后续可使用 bin/start.sh或bin/start.bat启动。
#### 安装教程
#### 文档
1. xxxx
2. xxxx
3. xxxx
https://open-iita.feishu.cn/wiki/Km67wBKNNiFMrMkldCJcxa6vnLe
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
# **商业版双节大优惠,惊喜价格!!!!**
#### 参与贡献
商务或技术交流请联系:
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
![输入图片说明](doc/image.png)
![输入图片说明](doc/%E9%9B%86%E7%BE%A4.png)
#### 捐助与支持
如果您觉得我的开源软件对你有所帮助请关注、star、fork :kissing_heart: 。
#### 特技
如果我的开源软件应用到您的商业项目中,请务必通知到我,因为得到用户的认可是支撑开源的动力。
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>iot-iita-plugins</artifactId>
<groupId>cc.iotkit.plugins</groupId>
<version>2.10.30</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>emqx-plugin</artifactId>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mqtt</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-proxy</artifactId>
<version>${vertx.version}</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<plugin.build.mode>dev</plugin.build.mode>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<plugin.build.mode>prod</plugin.build.mode>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>${spring-brick.version}</version>
<configuration>
<mode>${plugin.build.mode}</mode>
<pluginInfo>
<id>emqx-plugin</id>
<bootstrapClass>cc.iotkit.plugins.emqx.Application</bootstrapClass>
<version>${project.version}</version>
<provider>iita</provider>
<description>emqx示例插件</description>
<configFileName>application.yml</configFileName>
</pluginInfo>
<prodConfig>
<packageType>jar</packageType>
</prodConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,19 @@
package cc.iotkit.plugins.emqx;
import com.gitee.starblues.bootstrap.SpringPluginBootstrap;
import com.gitee.starblues.bootstrap.annotation.OneselfConfig;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author sjg
*/
@SpringBootApplication(scanBasePackages = "cc.iotkit.plugins.emqx")
@OneselfConfig(mainConfigFileName = {"application.yml"})
@EnableConfigurationProperties
public class Application extends SpringPluginBootstrap {
public static void main(String[] args) {
new Application().run(Application.class, args);
}
}

@ -0,0 +1,39 @@
package cc.iotkit.plugins.emqx.conf;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class BCDClockGenerator {
// BCD编码映射表0-9
private static final byte[] BCD_TABLE = {
0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09
};
public static byte[] generateBCDTime() {
LocalDateTime now = LocalDateTime.now();
String timeStr = now.format(DateTimeFormatter.ofPattern("yyMMddHHmmss"));
byte[] bcdBytes = new byte[6]; // 6字节存储YYMMDDhhmmss
for (int i = 0; i < 6; i++) {
int high = Character.digit(timeStr.charAt(i*2), 10);
int low = Character.digit(timeStr.charAt(i*2+1), 10);
bcdBytes[i] = (byte) ((BCD_TABLE[high] << 4) | BCD_TABLE[low]);
}
return bcdBytes;
}
public static String formatBCDToHex(byte[] bcd) {
StringBuilder sb = new StringBuilder();
for (byte b : bcd) {
sb.append(String.format("%02X ", b));
}
return sb.toString().replaceAll("\\s+", "");
}
/*public static void main(String[] args) {
byte[] bcdTime = generateBCDTime();
System.out.println(formatBCDToHex(bcdTime));
}*/
}

@ -0,0 +1,28 @@
package cc.iotkit.plugins.emqx.conf;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.LocalPluginConfig;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugins.emqx.service.FakeThingService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @author sjg
*/
@Component
public class BeanConfig {
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IThingService getThingService() {
return new FakeThingService();
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginConfig getPluginConfig(){
return new LocalPluginConfig();
}
}

@ -0,0 +1,87 @@
package cc.iotkit.plugins.emqx.conf;
public class CRC16ModbusUtil {
private static final int POLY = 0xA001;
private static final int INIT_VALUE = 0xFFFF;
public static String calculate(String hexStr) {
byte[] data = hexToByteArray(hexStr);
int crc = calculateCrcWithSwap(data);
return String.format("%04X", crc);
}
private static int calculateCrcWithSwap(byte[] data) {
int crc = 0xFFFF; // INIT_VALUE应为0xFFFF
for (byte b : data) {
crc ^= (b & 0xFF);
for (int i = 0; i < 8; i++) {
boolean lsb = (crc & 1) == 1;
crc >>>= 1;
if (lsb) crc ^= 0xA001; // POLY应为0xA001
}
}
return ((crc & 0xFF) << 8) | ((crc >> 8) & 0xFF); // 关键交换逻辑
}
private static int calculateCrc(byte[] data) {
int crc = INIT_VALUE;
for (byte b : data) {
crc ^= (b & 0xFF);
for (int i = 0; i < 8; i++) {
boolean lsb = (crc & 1) == 1;
crc >>>= 1;
if (lsb) crc ^= POLY;
}
}
return crc;
}
private static byte[] hexToByteArray(String hexStr) {
if (hexStr.length() % 2 != 0) {
throw new IllegalArgumentException("Hex string must have even length");
}
byte[] bytes = new byte[hexStr.length() / 2];
for (int i = 0; i < bytes.length; i++) {
String byteStr = hexStr.substring(2*i, 2*i+2);
bytes[i] = (byte) Integer.parseInt(byteStr, 16);
}
return bytes;
}
/**
*3.1
*
* 1 HEAD 0xaa
* 1 TYPE
* 1 VERSION
* 2 LENGTH
* 1 MID
* 1 CMD
* N DATA
* 2 CRC CRC16-MODBUS
* 1 TAIL 0x55
*
* */
public static String buildFrame(
String type, String version,
String mid, String cmd, String dataHex
) {
String headerLength = "AA" + type + version + mid + cmd + dataHex +"crc1"+ "55";
//额外加上长度的两个字节
Integer length =headerLength.length()/2 + 2;
String lengthHex = String.format("%04X", length);
String header = "AA" + type + version + lengthHex + mid + cmd;
// 转换为2字节16进制大端序
System.out.println("dataHex+++++++++++++++++++++++++++++++" + dataHex);
System.out.println("header+++++++++++++++++++++++++++++++" + header);
String crcData = lengthHex + mid + cmd + dataHex;
System.out.println("crcData+++++++++++++++++++++++++++++++" + crcData);
String crc = calculate(crcData);
return header + dataHex + crc + "55";
}
public static byte[] toBigEndianBytes(int length) {
if(length > 65535) throw new IllegalArgumentException("长度超过2字节范围");
return new byte[] {
(byte)((length >> 8) & 0xFF), // 高字节
(byte)(length & 0xFF) // 低字节
};
}
}

@ -0,0 +1,99 @@
package cc.iotkit.plugins.emqx.conf;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cc.iotkit.common.utils.HexUtil.hexStringToByteArray;
public class DeviceStatusParser {
private static final int COMMAND_CODE = 0x11;
public static Map<String, Object> parseStatusReportToMap(String hexData) {
byte[] data = GasAlarmDataParser.hexStringToByteArray(hexData);
return parseStatusReport(data);
}
public static Map<String, Object> parseStatusReport(byte[] hexData) {
Map<String, Object> result = new HashMap<>();
ByteBuffer buffer = ByteBuffer.wrap(hexData);
// 验证命令码
/* int cmd = buffer.get() & 0xFF;
if (cmd != COMMAND_CODE) {
throw new IllegalArgumentException("Invalid command code: 0x" + Integer.toHexString(cmd));
}*/
// 解析终端类型 (2字节)
int deviceType = buffer.getShort() & 0xFFFF;
result.put("deviceType", deviceType);
// 解析设备编号IMEI (15字节) 对应程序的
byte[] imeiBytes = new byte[15];
buffer.get(imeiBytes);
result.put("imei", new String(imeiBytes).trim());
// 解析ICCID卡号 (20字节)
byte[] iccidBytes = new byte[20];
buffer.get(iccidBytes);
result.put("iccid", new String(iccidBytes).trim());
// 解析软件版本 (4字节)
byte[] versionBytes = new byte[4];
buffer.get(versionBytes);
result.put("version", bytesToVersion(versionBytes));
// 解析通信模式 (1字节)
int commMode = buffer.get() & 0xFF;
result.put("communicationMode", commMode);
// 解析信号强度 (1字节)
int signal = buffer.get() & 0xFF;
result.put("signalStrength", signal);
return result;
}
private static String getDeviceTypeName(int type) {
switch (type) {
case 0: return "家用报警器";
case 1: return "独立式报警器";
case 2: return "工商业控制器";
case 3: return "动火离人设备";
default: return "未知设备类型";
}
}
private static String getCommModeName(int mode) {
switch (mode) {
case 0: return "NB-IoT";
case 1: return "4G";
case 2: return "WiFi";
default: return "未知通信模式";
}
}
private static String bytesToVersion(byte[] bytes) {
return String.format("%d.%d.%d.%d",
bytes[0] & 0xFF,
bytes[1] & 0xFF,
bytes[2] & 0xFF,
bytes[3] & 0xFF);
}
public static void main(String[] args) {
// 示例16进制数据 (命令码0x11 + 测试数据)
byte[] testData = new byte[] {
// 0x11, // 命令码
0x00, 0x02, // 工商业控制器
'8','6','1','2','3','4','0','0','0','0','1','2','3','4','5', // IMEI
'8','9','8','6','0','0','0','0','0','0','0','0','0','0','0','1','2','3','4','5', // ICCID
0x01, 0x02, 0x03, 0x04, // 软件版本1.2.3.4
0x01, // 4G通信
0x5A,0X5A // 信号强度90
};
Map<String, Object> result = DeviceStatusParser.parseStatusReport(testData);
System.out.println("解析结果: " + result);
}
}

@ -0,0 +1,63 @@
package cc.iotkit.plugins.emqx.conf;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class EventTypeMapper {
private static final Map<Integer, String> EVENT_TYPE_MAP = new ConcurrentHashMap<>();
static {
// 节点相关事件
EVENT_TYPE_MAP.put(1, "首警高");
EVENT_TYPE_MAP.put(2, "首警低");
EVENT_TYPE_MAP.put(3, "报警高");
EVENT_TYPE_MAP.put(4, "报警低");
EVENT_TYPE_MAP.put(5, "低限报警恢复");
EVENT_TYPE_MAP.put(6, "高限报警恢复");
EVENT_TYPE_MAP.put(7, "探头传感器故障");
EVENT_TYPE_MAP.put(8, "节点通讯断网故障");
EVENT_TYPE_MAP.put(9, "探头传感器故障恢复");
EVENT_TYPE_MAP.put(10, "节点通讯断网故障恢复");
EVENT_TYPE_MAP.put(11, "节点自检");
EVENT_TYPE_MAP.put(12, "节点自检完成");
EVENT_TYPE_MAP.put(13, "模块联动");
EVENT_TYPE_MAP.put(14, "阀门动作");
EVENT_TYPE_MAP.put(15, "浓度变化上报");
EVENT_TYPE_MAP.put(16, "预热");
EVENT_TYPE_MAP.put(17, "预热完成上报");
EVENT_TYPE_MAP.put(18, "寿命到期上报");
EVENT_TYPE_MAP.put(19, "新节点连接");
EVENT_TYPE_MAP.put(20, "节点联动恢复");
// 控制器相关事件
EVENT_TYPE_MAP.put(30, "自检");
EVENT_TYPE_MAP.put(31, "备电故障");
EVENT_TYPE_MAP.put(32, "主电故障");
EVENT_TYPE_MAP.put(33, "主电欠压");
EVENT_TYPE_MAP.put(34, "控制器复位");
EVENT_TYPE_MAP.put(35, "控制器开机");
EVENT_TYPE_MAP.put(36, "主电故障恢复");
EVENT_TYPE_MAP.put(37, "备电故障恢复");
EVENT_TYPE_MAP.put(38, "主电欠压恢复");
// 动火离人相关事件
EVENT_TYPE_MAP.put(50, "设备开机");
EVENT_TYPE_MAP.put(51, "动火离人报警");
EVENT_TYPE_MAP.put(52, "动火离人报警恢复");
EVENT_TYPE_MAP.put(53, "设备故障");
EVENT_TYPE_MAP.put(54, "设备故障恢复");
EVENT_TYPE_MAP.put(55, "断网故障");
EVENT_TYPE_MAP.put(56, "断网故障恢复");
// 正常上报事件
EVENT_TYPE_MAP.put(128, "燃气报警器正常数据上报");
EVENT_TYPE_MAP.put(129, "动火离人正常数据上报");
EVENT_TYPE_MAP.put(130, "联动控制箱正常数据上报");
EVENT_TYPE_MAP.put(151, "上报输出反馈状态变化");
}
public static String getEventTypeName(int eventType) {
return EVENT_TYPE_MAP.getOrDefault(eventType, "未知事件类型");
}
}

@ -0,0 +1,110 @@
package cc.iotkit.plugins.emqx.conf;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
public class FireSafetyDataParser {
// 事件类型常量
public static final int EVENT_POWER_ON = 50;
public static final int EVENT_FIRE_ALARM = 51;
public static final int EVENT_ALARM_RESET = 52;
public static final int EVENT_DEVICE_FAIL = 53;
public static final int EVENT_FAIL_RESET = 54;
public static final int EVENT_NET_DOWN = 55;
public static final int EVENT_NET_RECOVER = 56;
// 数据项ID常量
public static final int ITEM_TEMP_DISTANCE = 0x0040;
public static final int ITEM_ALARM_DURATION = 0x0041;
public static final int ITEM_SOUP_ALARM = 0x0042;
public static final int ITEM_VOLUME = 0x0043;
public static final int ITEM_SOUP_MODE = 0x0044;
public static final int ITEM_ALARM_STATUS = 0x0045;
public static final int ITEM_FLAME_DETECT = 0x0046;
public static final int ITEM_HUMAN_DETECT = 0x0047;
public static final int ITEM_ERROR_CODE = 0x0048;
public static Map<String, Object> parseData(byte[] rawData) {
ByteBuffer buffer = ByteBuffer.wrap(rawData);
Map<String, Object> result = new HashMap<>();
result.put("dataType", "HOT_WORK_DATA");
// 解析事件头1字节事件类型 + 6字节BCD时间
// int eventType = buffer.get() & 0xFF;
int eventType = buffer.getShort() & 0xFFFF;
// buffer.getShort() & 0xFFFF
result.put("eventType", eventType);
result.put("timestamp", GasAlarmDataParser.parseBcdTime(buffer));
result.put("eventTypeValue", EventTypeMapper.getEventTypeName(Integer.parseInt(result.get("eventType").toString())));
// 解析数据域
if (buffer.remaining() > 0) {
int dataLength = buffer.get() & 0xFF;
while (buffer.remaining() >= 4) { // 每个数据项至少4字节2字节ID+2字节数据
int itemId = buffer.getShort() & 0xFFFF;
int itemValue = buffer.getShort() & 0xFFFF;
result.put(getItemName(itemId), parseItemValue(itemId, itemValue));
}
}
return result;
}
/* private static String parseBcdTime(ByteBuffer buffer) {
byte[] bcdTime = new byte[6];
buffer.get(bcdTime);
return String.format("20%02d-%02d-%02d %02d:%02d:%02d",
bcdTime[0], bcdTime[1], bcdTime[2], bcdTime[3], bcdTime[4], bcdTime[5]);
}*/
private static String getItemName(int itemId) {
switch (itemId) {
case ITEM_TEMP_DISTANCE: return "temperatureDistance";
case ITEM_ALARM_DURATION: return "alarmDuration";
case ITEM_SOUP_ALARM: return "soupAlarmTime";
case ITEM_VOLUME: return "volumeLevel";
case ITEM_SOUP_MODE: return "soupMode";
case ITEM_ALARM_STATUS: return "alarmStatus";
case ITEM_FLAME_DETECT: return "flameDetected";
case ITEM_HUMAN_DETECT: return "humanDetected";
case ITEM_ERROR_CODE: return "errorCode";
default: return "unknown_" + Integer.toHexString(itemId);
}
}
private static Object parseItemValue(int itemId, int value) {
switch (itemId) {
// 测温距离(0x0040)
case ITEM_TEMP_DISTANCE:
return new String[]{"2米","2-3米","3-3.5米"}[value-1];
// 动火报警时间(0x0041)
case ITEM_ALARM_DURATION:
return value + "分钟";
// 煲汤报警时间(0x0042)
case ITEM_SOUP_ALARM:
return (value > 60) ?
(value/60 + "小时" + value%60 + "分钟") :
(value + "分钟");
// 音量控制(0x0043)
case ITEM_VOLUME:
return new String[]{"静音","小","中","大"}[value];
// 煲汤模式(0x0044)
case ITEM_SOUP_MODE:
return value == 0 ? "普通模式" : "煲汤模式";
// 报警状态(0x0045)
case ITEM_ALARM_STATUS:
return value == 0 ? "未报警" : "报警中";
// 火苗检测(0x0046)
case ITEM_FLAME_DETECT:
return value == 0 ? "未检测到火苗" : "检测到火苗";
// 人体检测(0x0047)
case ITEM_HUMAN_DETECT:
return value == 0 ? "无人员" : "有人员";
// 故障码(预留)
case ITEM_ERROR_CODE:
return String.format("故障码0x%04X", value);
default:
return value;
}
}
}

@ -0,0 +1,187 @@
package cc.iotkit.plugins.emqx.conf;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
public class FireSafetyDataReceiver {
public static void main(String[] args) {
// 测试16进制报文17(事件类型) + 250616123456(时间) + 02(数据长度) + 0040(测温距离ID) + 0002(2-3米)
String hexData = "17 25 06 16 12 34 56 02 00 40 00 02";
Map<String, Object> result = handleHexData(hexData);
System.out.println("解析结果:" + result);
result.forEach((k, v) -> System.out.println(k + ": " + v));
}
/*public void processDataStream(InputStream inputStream) throws Exception {
byte[] header = new byte[HEADER_SIZE];
while (inputStream.read(header) == HEADER_SIZE) {
int dataLength = inputStream.read();
byte[] payload = new byte[dataLength];
inputStream.read(payload);
byte[] fullData = new byte[HEADER_SIZE + 1 + dataLength];
System.arraycopy(header, 0, fullData, 0, HEADER_SIZE);
fullData[HEADER_SIZE] = (byte)dataLength;
System.arraycopy(payload, 0, fullData, HEADER_SIZE+1, dataLength);
Map<String, Object> parsedData = FireSafetyDataParser.parseData(fullData);
handleEvent(parsedData);
}
}*/
public static Map<String, Object> handleHexData(String hexString) {
byte[] data = hexStringToBytes(hexString);
if (data == null || data.length < 1) {
return null;
}
int eventType = Byte.toUnsignedInt(data[0]) + Byte.toUnsignedInt(data[1]);
if (eventType == 129) {
return handleEvent(data);
}
return OpenFireHandleEvent(data);
}
private static byte[] hexStringToBytes(String hex) {
hex = hex.replaceAll("\\s", "");
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
return bytes;
}
/*
*
* */
public static Map<String, Object> OpenFireHandleEvent(byte[] data) {
Map<String, Object> result= new HashMap<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
result.put("dataType", "HOT_WORK_DATA");
int eventType = buffer.getShort() & 0xFFFF;
// 解析事件头
//int eventType = buffer.get() & 0xFF;
result.put("eventType", eventType);
result.put("timestamp", GasAlarmDataParser.parseBcdTime(buffer));
result.put("eventTypeValue", EventTypeMapper.getEventTypeName(Integer.parseInt(result.get("eventType").toString())));
// 完整事件处理
/* switch (eventType) {
case FireSafetyDataParser.EVENT_POWER_ON:
result.put("eventName", "设备开机");
// result.put("action", "记录启动日志");
break;
case FireSafetyDataParser.EVENT_FIRE_ALARM:
result.put("eventName", "动火离人报警");
//result.put("action", "触发声光报警+推送告警");
// parseDataItems(buffer, result);
break;
case FireSafetyDataParser.EVENT_ALARM_RESET:
result.put("eventName", "动火离人报警恢复");
// result.put("action", "停止报警+记录处置");
break;
case FireSafetyDataParser.EVENT_DEVICE_FAIL:
result.put("eventName", "设备故障");
// result.put("action", "启动自检+通知维护");
// parseDataItems(buffer, result);
break;
case FireSafetyDataParser.EVENT_FAIL_RESET:
result.put("eventName", "设备故障恢复");
// result.put("action", "更新设备状态");
break;
case FireSafetyDataParser.EVENT_NET_DOWN:
result.put("eventName", "断网故障");
// result.put("action", "启用本地存储");
break;
case FireSafetyDataParser.EVENT_NET_RECOVER:
result.put("eventName", "断网故障恢复");
// result.put("action", "同步缓存数据");
break;
default:
result.put("eventName", "未知事件");
// result.put("action", "需要人工核查");
}*/
System.out.println("解析后的数据" + result);
return result;
}
/*
*
* */
public static Map<String, Object> handleEvent(byte[] data) {
// Map<String, Object> result = new HashMap<>();
Map<String, Object> result= FireSafetyDataParser.parseData(data);
/* ByteBuffer buffer = ByteBuffer.wrap(data);
// 解析事件头
int eventType = buffer.get() & 0xFF;
result.put("eventType", eventType);
result.put("timestamp", GasAlarmDataParser.parseBcdTime(buffer));
// 完整事件处理
switch (eventType) {
case FireSafetyDataParser.EVENT_POWER_ON:
result.put("eventName", "设备开机");
// result.put("action", "记录启动日志");
break;
case FireSafetyDataParser.EVENT_FIRE_ALARM:
result.put("eventName", "动火离人报警");
//result.put("action", "触发声光报警+推送告警");
// parseDataItems(buffer, result);
break;
case FireSafetyDataParser.EVENT_ALARM_RESET:
result.put("eventName", "动火离人报警恢复");
// result.put("action", "停止报警+记录处置");
break;
case FireSafetyDataParser.EVENT_DEVICE_FAIL:
result.put("eventName", "设备故障");
// result.put("action", "启动自检+通知维护");
// parseDataItems(buffer, result);
break;
case FireSafetyDataParser.EVENT_FAIL_RESET:
result.put("eventName", "设备故障恢复");
// result.put("action", "更新设备状态");
break;
case FireSafetyDataParser.EVENT_NET_DOWN:
result.put("eventName", "断网故障");
// result.put("action", "启用本地存储");
break;
case FireSafetyDataParser.EVENT_NET_RECOVER:
result.put("eventName", "断网故障恢复");
// result.put("action", "同步缓存数据");
break;
default:
result.put("eventName", "未知事件");
// result.put("action", "需要人工核查");
}*/
System.out.println("解析后的数据" + result);
return result;
}
/* private static String parseBcdTime(byte[] data) {
return String.format("20%02d-%02d-%02d %02d:%02d:%02d",
bcdToInt(data[0]), bcdToInt(data[1]), bcdToInt(data[2]),
bcdToInt(data[3]), bcdToInt(data[4]), bcdToInt(data[5]));
}
private static int bcdToInt(byte b) {
return ((b >> 4) & 0x0F)*10 + (b & 0x0F);
}*/
/*
private static String parseBcdTime(ByteBuffer buffer) {
byte[] timeBytes = new byte[6];
buffer.get(timeBytes);
return String.format("20%02d-%02d-%02d %02d:%02d:%02d",
timeBytes[0], timeBytes[1], timeBytes[2],
timeBytes[3], timeBytes[4], timeBytes[5]);
}
*/
private static void parseDataItems(ByteBuffer buffer, Map<String, Object> result) {
if (buffer.remaining() > 0) {
int dataLength = buffer.get() & 0xFF;
while (buffer.remaining() >= 4) {
int itemId = buffer.getShort() & 0xFFFF;
int value = buffer.getShort() & 0xFFFF;
result.put("item_" + Integer.toHexString(itemId), value);
}
}
}
}

@ -0,0 +1,306 @@
package cc.iotkit.plugins.emqx.conf;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.*;
public class GasAlarmDataParser {
public static void main(String[] args) {
// 测试数据250707131758 -> 对应字节数组 {0x25, 0x07, 0x07, 0x13, 0x17, 0x58}
byte[] testData = new byte[]{0x25, 0x07, 0x07, 0x13, 0x17, 0x58};
ByteBuffer buffer = ByteBuffer.wrap(testData);
String result = parseBcdTime(buffer);
System.out.println("解析结果: " + result);
// 节点相关事件测试数据 (1-12)
// 节点相关事件测试数据 (1-12) - 数值部分改为低位在前
String[] nodeEvents = {
"00012025052710253000010002", // 1-首警高 (数值0001→0100)
"00022025052710253000020003", // 2-首警低
"00032025052710253000030004", // 3-报警高
"00042025052710253000040005", // 4-报警低
"00052025052710253000050006", // 5-低限报警恢复
"00062025052710253000060007", // 6-高限报警恢复
"00072025052710253000070008", // 7-探头传感器故障
"00082025052710253000080009", // 8-节点通讯断网故障
"0009202505271025300009000A", // 9-探头传感器故障恢复
"000A20250527102530000A000B", // 10-节点通讯断网故障恢复
"000B20250527102530000B000C", // 11-自检/模块联动
"000C20250527102530000C000D" // 12-阀门动作
};
// 控制器相关事件测试数据 (13-21) - 时间部分保持原样
String[] controllerEvents = {
"000D20250527102530", // 13-自检
"000E20250527102530", // 14-备电故障
"000F20250527102530", // 15-主电故障
"001020250527102530", // 16-主电欠压
"001120250527102530", // 17-控制器复位
"001220250527102530", // 18-控制器开机
"001320250527102530", // 19-主电故障恢复
"001420250527102530", // 20-备电故障恢复
"001520250527102530" // 21-主电欠压恢复
};
// 动火离人相关事件测试数据 (22-28)
String[] fireMonitorEvents = {
"00162025052710253000010002", // 22-设备开机
"00172025052710253000020003", // 23-动火离人报警
"00182025052710253000030004", // 24-动火离人报警恢复
"00192025052710253000040005", // 25-设备故障
"001A2025052710253000050006", // 26-设备故障恢复
"001B2025052710253000060007", // 27-断网故障
"001C2025052710253000070008" // 28-断网故障恢复
};
// 正常数据上报测试数据 (128-129)
String[] normalReports = {
"0080202505271025300003000200010203040005000600070008", // 128-燃气报警器
"01812025052710253000020001000102030400050006" // 129-动火离人
};
testEventGroup("节点事件", nodeEvents);
testEventGroup("控制器事件", controllerEvents);
testEventGroup("动火离人事件", fireMonitorEvents);
testEventGroup("正常上报", normalReports);
}
private static void testEventGroup(String groupName, String[] testCases) {
System.out.println("\n=== " + groupName + "测试 ===");
for (String hexData : testCases) {
try {
Map<String, Object> result = GasAlarmDataParser.parseHexDataToList(hexData);
System.out.println("原始数据: " + hexData);
System.out.println("解析结果: " + result.get("eventType")
+ " - " + result.get("dataType"));
System.out.println("rrr解析结果: " + result.get(0));
} catch (Exception e) {
System.out.println("解析异常: " + e.getMessage());
}
}
}
public static Map<String, Object> parseHexDataToList(String hexData) {
System.out.println("parseHexDataToList: " + hexData);
byte[] data = hexStringToByteArray(hexData);
return parseDataToList(data);
}
public static Map<String, Object> parseDataToList(byte[] data) {
Map<String, Object> resultList = new HashMap<>();
if (data == null || data.length < 1) {
return resultList;
}
int eventType = Byte.toUnsignedInt(data[0]) + Byte.toUnsignedInt(data[1]);
System.out.println("eventType: " + eventType);
if (eventType == 128) {
// // 正常数据上报 (NORMAL_DATA)
//4.2.3 终端上报连接节点数据域格式
//此数据域主要用于当“家用报警器”、“独立式报警器”和“工商业控制器”定时上报数据。当设备首次上电时,也发送此数据域数据。
return parseNormalDataToList(data);
} else if (isControllerEvent(eventType)) {
// 4.2.1 终端上报控制器事件数据域格式
// 控制器事件 (CONTROLLER_EVENT)
return parseControllerEventToList(data);
}
//4.2.2 终端上报节点事件数据域格式
// 节点事件 (NODE_EVENT)
return parseNodeEventToList(data);
}
private static Map<String, Object> parseControllerEventToList(byte[] data) {
// List<Map<String, Object>> eventList = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put("dataType", "CONTROLLER_EVENT");
int eventType = buffer.getShort() & 0xFFFF;
eventMap.put("eventType", eventType);
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
eventMap.put("timestamp", parseBcdTime(buffer));
// eventList.add(eventMap);
return eventMap;
}
private static Map<String, Object> parseNodeEventToList(byte[] data) {
// Map<String, Object> eventList = new HashMap<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put("dataType", "NODE_EVENT");
int eventType = buffer.getShort() & 0xFFFF;
eventMap.put("eventType", eventType);
if (!Objects.equals(eventMap.get("eventType"), 15)) {
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
eventMap.put("timestamp", parseBcdTime(buffer));
eventMap.put("nodeId", buffer.getShort() & 0xFFFF);
// BigDecimal decimalValue = new BigDecimal(buffer.getShort() & 0xFFFF)
// .divide(new BigDecimal(1), 4, RoundingMode.HALF_UP).stripTrailingZeros();
eventMap.put("dataValues", buffer.getShort() & 0xFFFF);
// eventMap.put("dataDetectorValue" , eventMap.get("dataValues"));
} else {
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
eventMap.put("timestamp", parseBcdTime(buffer));
eventMap.put("totalNodes", buffer.get() & 0xFF);
eventMap.put("nodeId", buffer.getShort() & 0xFFFF);
System.out.println("浓度变化的剩余长度"+buffer.remaining());
// int nodeCounter = 0;
while (buffer.remaining() >= 2) {
// String suffix = String.valueOf(nodeCounter++);
// BigDecimal decimalValue = new BigDecimal(buffer.getShort() & 0xFFFF)
// .divide(new BigDecimal(1), 4, RoundingMode.HALF_UP).stripTrailingZeros();
eventMap.put("dataValues", buffer.getShort() & 0xFFFF);
// 0节点为探测器
eventMap.put("dataDetectorValue" , eventMap.get("dataValues"));
}
}
System.out.println("eventMap+++++++++++: " + eventMap);
//eventList.add(eventMap);
return eventMap;
}
private static Map<String, Object> parseNormalDataToList(byte[] data) {
// List<Map<String, Object>> dataList = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
// 基础信息
Map<String, Object> baseInfo = new LinkedHashMap<>();
baseInfo.put("dataType", "NORMAL_DATA");
int eventType = buffer.getShort() & 0xFFFF;
baseInfo.put("eventType", eventType);
baseInfo.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
baseInfo.put("timestamp", parseBcdTime(buffer));
baseInfo.put("totalNodes", buffer.getShort() & 0xFFFF);
baseInfo.put("reportedNodes", buffer.get() & 0xFF);
// dataList.add(baseInfo);
System.out.println(buffer.remaining());
// 节点数据
// 节点数据(动态添加数字后缀)
int nodeCounter = 0;
while (buffer.remaining() >= 7) {
String suffix = String.valueOf(nodeCounter++);
// Map<String, Object> nodeMap = new LinkedHashMap<>();
baseInfo.put("nodeId" + suffix, buffer.getShort() & 0xFFFF);
baseInfo.put("nodeType" + suffix, buffer.get() & 0xFF);
// nodeMap.put("unit", buffer.get() & 0xFF); // 节点节点参数
// nodeMap.put("precision", buffer.get() & 0xFF);
baseInfo.put("gasType" + suffix, GasDetectorParser.parse(buffer.getShort() & 0xFFFF));
Integer dataValue = buffer.getShort() & 0xFFFF;
if (Objects.equals(baseInfo.get("nodeType" + suffix), 0)) {
// BigDecimal decimalValue = new BigDecimal(dataValue)
// .divide(new BigDecimal(1), 4, RoundingMode.HALF_UP).stripTrailingZeros();
baseInfo.put("dataValues" + suffix, dataValue);
// 0节点为探测器
baseInfo.put("dataDetectorValue" + suffix, baseInfo.get("dataValues" + suffix));
} else if (Objects.equals(baseInfo.get("nodeType" + suffix), 1)) {
baseInfo.put("dataValues" + suffix, dataValue);
//1节点为输出模块
baseInfo.put("dataSinksValue" + suffix, baseInfo.get("dataValues" + suffix).equals("0") ? "未动作" : "已动作");
}
// dataList.add(nodeMap);
}
return baseInfo;
}
// 辅助方法:字节数组转十六进制字符串
private static String bytesToHex(byte[] bytes) {
StringBuilder hexSb = new StringBuilder();
for (byte b : bytes) {
hexSb.append(String.format("%02X", b));
}
return hexSb.toString();
}
// 保留原有辅助方法
/*public static String parseBcdTime(ByteBuffer buffer) {
// 打印ByteBuffer的十六进制表示
System.out.println("ByteBuffer内容十六进制: " + bytesToHex(buffer.array()));
byte[] bcdTime = new byte[6];
buffer.get(bcdTime);
StringBuilder sb = new StringBuilder();
for (byte b : bcdTime) {
// 提取高四位和低四位,并转换为十进制数字
int high = (b >> 4) & 0x0F;
int low = b & 0x0F;
// 组合成两位十进制字符串
sb.append(String.format("%1d%1d", high, low));
}
String bcdStr = sb.toString();
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
sdf.set2DigitYearStart(new Date(946684800000L)); // 2000-01-01
Date date = sdf.parse(bcdStr);
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
} catch (Exception e) {
return bcdStr; // 返回原始BCD字符串可能包含越界值
}
}*/
public static String parseBcdTime(ByteBuffer buffer) {
// 打印ByteBuffer的十六进制表示
System.out.println("ByteBuffer内容十六进制: " + bytesToHex(buffer.array()));
byte[] bcdTime = new byte[6];
buffer.get(bcdTime);
StringBuilder sb = new StringBuilder();
for (byte b : bcdTime) {
sb.append(String.format("%02X", b));
}
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
sdf.set2DigitYearStart(new SimpleDateFormat("yyyy").parse("2000")); // 设置世纪转折点
Date date = sdf.parse(sb.toString());
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
} catch (Exception e) {
return sb.toString();
}
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
private static boolean isControllerEvent(int eventType) {
return eventType >= 30 && eventType <= 38;
}
}

@ -0,0 +1,44 @@
package cc.iotkit.plugins.emqx.conf;
import java.util.Map;
public class GasDetectorParser {
// 探测器精度映射
private static final Map<Integer, String> PRECISION_MAP = Map.of(
0b000, "*1",
0b001, "*0.1",
0b010, "*0.01",
0b011, "*0.001",
0b100, "*0.0001"
);
// 浓度单位映射
private static final Map<Integer, String> UNIT_MAP = Map.of(
0b000, "%LEL",
0b001, "PPM",
0b010, "%VOL"
);
// 气体类型映射
private static final Map<Integer, String> GAS_TYPE_MAP = Map.of(
0b0000, "甲烷",
0b0001, "一氧化碳",
0b0010, "丙烷",
0b0011, "氢气",
0b0100, "苯",
0b0101, "氧气"
);
public static String parse(int data) {
int precisionBits = (data >> 13) & 0b111; // 提取bit15-13
int unitBits = (data >> 10) & 0b111; // 提取bit12-10
int gasTypeBits = (data >> 6) & 0b1111; // 提取bit9-6
return String.format("探测器精度: %s, 浓度单位: %s, 测量气体: %s",
PRECISION_MAP.getOrDefault(precisionBits, "未知精度"),
UNIT_MAP.getOrDefault(unitBits, "未知单位"),
GAS_TYPE_MAP.getOrDefault(gasTypeBits, "未知气体")
);
}
}

@ -0,0 +1,275 @@
package cc.iotkit.plugins.emqx.conf;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
public class IoTConfigProtocol {
// 配置项常量定义(完整版)
public static final int IP_REALM = 0x0000;
public static final int PORT = 0x0001;
public static final int REPORT_AWAIT_TIME = 0x0002;
public static final int REPORT_RETRY_TIMES = 0x0003;
public static final int CARD = 0x0004;
public static final int USERNAME = 0x0010;
public static final int PASSWORD = 0x0011;
public static final int KEEP_ALIVE = 0x0012;
public static final int PUBLISH_TOPIC = 0x0016;
public static final int SUBSCRIBE_TOPIC = 0x0017;
public static final int REPORT_RATE_UPPER = 0x0021;
public static final int REPORT_CONCENTRATION_RANGE = 0x0022;
public static final int REPORT_RATE_LOWER = 0x0023;
public static final int SIGNAL = 0x0024;
public static final int TIME_SYNC = 0x0025;
public static final int VERSION = 0x0026;
public static final int REPORT_RATE_LinkConstraint = 0x0100;
// 生成读取指令
// 反向映射Map
private static final Map<Integer, String> CONFIG_ID_TO_KEY = new HashMap<>();
static {
// 初始化反向映射
CONFIG_ID_TO_KEY.put(IP_REALM, "ip");
CONFIG_ID_TO_KEY.put(PORT, "port");
CONFIG_ID_TO_KEY.put(REPORT_AWAIT_TIME, "reportAwaitTime");
CONFIG_ID_TO_KEY.put(REPORT_RETRY_TIMES, "reportRetryTimes");
CONFIG_ID_TO_KEY.put(CARD, "card");
CONFIG_ID_TO_KEY.put(USERNAME, "username");
CONFIG_ID_TO_KEY.put(PASSWORD, "password");
CONFIG_ID_TO_KEY.put(KEEP_ALIVE, "keepAlive");
CONFIG_ID_TO_KEY.put(PUBLISH_TOPIC, "publishTopicPrefix");
CONFIG_ID_TO_KEY.put(SUBSCRIBE_TOPIC, "subscribeTopicPrefix");
CONFIG_ID_TO_KEY.put(REPORT_RATE_UPPER, "reportRateUpper");
CONFIG_ID_TO_KEY.put(REPORT_CONCENTRATION_RANGE, "reportConcentrationRange");
CONFIG_ID_TO_KEY.put(REPORT_RATE_LOWER, "reportRateLower");
CONFIG_ID_TO_KEY.put(SIGNAL, "signal");
CONFIG_ID_TO_KEY.put(TIME_SYNC, "timeSyncInternal");
CONFIG_ID_TO_KEY.put(VERSION, "version");
CONFIG_ID_TO_KEY.put(REPORT_RATE_LinkConstraint, "controlOutput");
}
public static String getKeyByConfigId(int configId) {
return CONFIG_ID_TO_KEY.get(configId);
}
public static String generateReadCommand( int configId) {
ByteBuffer buf = ByteBuffer.allocate(2);
// buf.putShort((short) 0xAA23); // 起始符+命令码
// buf.putShort((short) 4); // 数据长度
// buf.putShort((short) terminalType);
buf.putShort((short) configId);
return bytesToHex(buf.array());
}
// 生成写入指令
public static String generateWriteCommand(int configId, byte[] data) {
ByteBuffer buf = ByteBuffer.allocate(3 + data.length);
//buf.putShort((short) (5 + data.length)); // 数据长度
// buf.putShort((short) terminalType);
buf.putShort((short) configId);
buf.put((byte) data.length); // 数据长度字节
buf.put(data); // 实际数据
return bytesToHex(buf.array());
}
// 完整读取响应解析
public static Map<String, Object> parseReadResponse(String hexResp) {
byte[] data = hexToBytes(hexResp);
ByteBuffer buf = ByteBuffer.wrap(data);
Map<String, Object> result = new HashMap<>();
result.put("commandCode", buf.get() & 0xFF);
result.put("configId", buf.getShort() & 0xFFFF);
int dataLen = buf.get() & 0xFF;
byte[] value = new byte[dataLen];
buf.get(value);
// 根据配置ID进行类型化解析
switch((int)result.get("configId")) {
case IP_REALM:
case PUBLISH_TOPIC:
case SUBSCRIBE_TOPIC:
result.put("value", parseTLVString(value));
break;
case PORT:
case KEEP_ALIVE:
case REPORT_RATE_UPPER:
case REPORT_CONCENTRATION_RANGE:
case REPORT_RATE_LOWER:
result.put("value", ByteBuffer.wrap(value).getShort());
break;
case REPORT_AWAIT_TIME:
case REPORT_RETRY_TIMES:
case SIGNAL:
result.put("value", value[0] & 0xFF);
break;
case CARD:
result.put("value", parseSimCard(value));
break;
case TIME_SYNC:
result.put("value", parseBcdTime(value));
break;
case VERSION:
result.put("value", parseVersion(value));
break;
default:
result.put("value", bytesToHex(value));
}
// CONFIG_ID_TO_KEY.get((int)result.get("configId"));
result.put("key", getKeyByConfigId((int)result.get("configId")));
return result;
}
/* // 特殊格式解析方法
private static String parseTLVString(byte[] data) {
int len = data[0] & 0xFF;
int maxAvailable = data.length - 1; // 可用最大长度
if (maxAvailable <= 0 || len <= 0) {
return ""; // 或抛出自定义异常
}
return new String(data, 1, Math.min(len, maxAvailable), StandardCharsets.US_ASCII);
}*/
private static String parseTLVString(byte[] data) {
int maxAvailable = data.length; // 可用最大长度
if (maxAvailable <= 0) {
return ""; // 或抛出自定义异常
}
return new String(data, StandardCharsets.US_ASCII);
}
private static String parseSimCard(byte[] data) {
// int len = data[0] & 0xFF;
int maxAvailable = data.length; // 可用最大长度
if (maxAvailable <= 0) {
return ""; // 或抛出自定义异常
}
return new String(data, StandardCharsets.US_ASCII);
}
public static String parseBcdTime(byte[] data) {
return String.format("20%02d-%02d-%02d %02d:%02d:%02d",
bcdToInt(data[0]), bcdToInt(data[1]), bcdToInt(data[2]),
bcdToInt(data[3]), bcdToInt(data[4]), bcdToInt(data[5]));
}
private static int bcdToInt(byte b) {
return ((b >> 4) & 0x0F)*10 + (b & 0x0F);
}
private static String parseVersion(byte[] data) {
return String.format("%d.%d.%d.%d",
data[0] & 0xFF, data[1] & 0xFF,
data[2] & 0xFF, data[3] & 0xFF);
}
// 字节数组与16进制转换工具
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
/*
* ipTLV
* */
public static byte[] buildTLVData(byte[] value) {
byte[] data = new byte[value.length + 1];
data[0] = (byte)value.length;
System.arraycopy(value, 0, data, 1, value.length);
return data;
}
/*
* ipTLV
* */
public static byte[] convertToFixedAscii(String input, int fixedLength) {
// 验证输入格式
if (!isValidIpOrDomain(input)) {
throw new IllegalArgumentException("Invalid IP/Domain format");
}
byte[] originBytes = input.getBytes(StandardCharsets.US_ASCII);
byte[] result = new byte[fixedLength];
// 复制原始数据并补0
System.arraycopy(originBytes, 0, result, 0, Math.min(originBytes.length, fixedLength));
return result;
}
private static boolean isValidIpOrDomain(String input) {
// IP地址正则验证 (IPv4)
String ipPattern = "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$";
// 域名正则验证 (支持国际化域名IDN的ASCII形式)
String domainPattern = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$";
return input.matches(ipPattern) || input.matches(domainPattern);
}
/*
*
* */
public static byte[] buildByteData(int value) {
return new byte[] { (byte)(value & 0xFF) };
}
// 构建字符串类型数据(带长度前缀)
public static byte[] buildStringData(String value, int maxLength) {
byte[] strBytes = value.getBytes(StandardCharsets.US_ASCII);
if(strBytes.length > maxLength) {
throw new IllegalArgumentException("Value exceeds max length");
}
// byte[] data = new byte[strBytes.length+1];
//data[0] = (byte) strBytes.length;
// System.arraycopy(strBytes, 0, strBytes, 1, strBytes.length);
return strBytes;
}
// 构建BCD时间数据(YYMMDDhhmmss)
public static byte[] buildBcdTimeData(String time) {
/* DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMddHHmmss");
String formatted = LocalDateTime.now().format(formatter);*/
// 验证输入格式
DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.parse(time, inputFormatter);
// 转换为yyMMddHHmmss格式字符串
String formatted = dateTime.format(DateTimeFormatter.ofPattern("yyMMddHHmmss"));
byte[] bcdData = new byte[6];
for(int i = 0; i < 6; i++) {
int pair = Integer.parseInt(formatted.substring(i*2, i*2+2));
bcdData[i] = (byte)(((pair / 10) << 4) | (pair % 10));
}
return bcdData;
}
// 构建版本号数据(4字节)
/* public static byte[] buildVersionData(String version) {
String[] parts = version.split("\\.");
if(parts.length != 4) throw new IllegalArgumentException();
byte[] data = new byte[4];
for(int i = 0; i < 4; i++) {
data[i] = (byte)Integer.parseInt(parts[i]);
}
return data;
}*/
private static byte[] hexToBytes(String hex) {
hex = hex.replaceAll("\\s", "");
byte[] data = new byte[hex.length()/2];
for (int i=0; i<hex.length(); i+=2) {
data[i/2] = (byte)Integer.parseInt(hex.substring(i,i+2),16);
}
return data;
}
}

@ -0,0 +1,248 @@
package cc.iotkit.plugins.emqx.conf;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.*;
public class LinkageControlDataParser {
private static void testEventGroup(String groupName, String[] testCases) {
System.out.println("\n=== " + groupName + "测试 ===");
for (String hexData : testCases) {
try {
Map<String, Object> result = LinkageControlDataParser.parseHexDataToList(hexData);
System.out.println("原始数据: " + hexData);
System.out.println("解析结果: " + result.get("eventType")
+ " - " + result.get("dataType"));
System.out.println("rrr解析结果: " + result.get(0));
} catch (Exception e) {
System.out.println("解析异常: " + e.getMessage());
}
}
}
public static Map<String, Object> parseHexDataToList(String hexData) {
System.out.println("parseHexDataToList: " + hexData);
byte[] data = hexStringToByteArray(hexData);
return parseDataToList(data);
}
public static Map<String, Object> parseDataToList(byte[] data) {
Map<String, Object> resultList = new HashMap<>();
if (data == null || data.length < 1) {
return resultList;
}
int eventType = Byte.toUnsignedInt(data[0]) + Byte.toUnsignedInt(data[1]);
System.out.println("eventType: " + eventType);
if (eventType == 130) {
// // 正常数据上报 (NORMAL_DATA)
return parseNormalDataToList(data);
} /*else if (isControllerEvent(eventType)) {
// 控制器事件 (CONTROLLER_EVENT)
return parseControllerEventToList(data);
}*/
// 节点事件 (NODE_EVENT)
return LinkageControlEventToList(data);
}
private static Map<String, Object> LinkageControlEventToList(byte[] data) {
// List<Map<String, Object>> eventList = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put("dataType", "LINKAGE_CONTROL_EVENT");
int eventType = buffer.getShort() & 0xFFFF;
eventMap.put("eventType", eventType);
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
eventMap.put("timestamp", parseBcdTime(buffer));
eventMap.put("nodeId", buffer.getShort() & 0xFFFF);
Integer dataValue = buffer.getShort() & 0xFFFF;
eventMap.put("dataValues", dataValue);
String status = (dataValue == 0) ? "不支持反馈" :
(dataValue == 1) ? "反馈未动作" :
(dataValue == 2) ? "反馈动作" : "未知状态";
eventMap.put("dataDetectorValue" , status);
return eventMap;
}
private static Map<String, Object> parseNodeEventToList(byte[] data) {
// Map<String, Object> eventList = new HashMap<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put("dataType", "NODE_EVENT");
int eventType = buffer.getShort() & 0xFFFF;
eventMap.put("eventType", eventType);
if (!Objects.equals(eventMap.get("eventType"), 15)) {
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
eventMap.put("timestamp", parseBcdTime(buffer));
eventMap.put("nodeId", buffer.getShort() & 0xFFFF);
// BigDecimal decimalValue = new BigDecimal(buffer.getShort() & 0xFFFF)
// .divide(new BigDecimal(1), 4, RoundingMode.HALF_UP).stripTrailingZeros();
eventMap.put("dataValues", buffer.getShort() & 0xFFFF);
// eventMap.put("dataDetectorValue" , eventMap.get("dataValues"));
} else {
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
eventMap.put("timestamp", parseBcdTime(buffer));
eventMap.put("totalNodes", buffer.get() & 0xFF);
eventMap.put("nodeId", buffer.getShort() & 0xFFFF);
System.out.println("浓度变化的剩余长度"+buffer.remaining());
// int nodeCounter = 0;
while (buffer.remaining() >= 2) {
// String suffix = String.valueOf(nodeCounter++);
// BigDecimal decimalValue = new BigDecimal(buffer.getShort() & 0xFFFF)
// .divide(new BigDecimal(1), 4, RoundingMode.HALF_UP).stripTrailingZeros();
eventMap.put("dataValues", buffer.getShort() & 0xFFFF);
// 0节点为探测器
eventMap.put("dataDetectorValue" , eventMap.get("dataValues"));
}
}
System.out.println("eventMap+++++++++++: " + eventMap);
//eventList.add(eventMap);
return eventMap;
}
private static Map<String, Object> parseNormalDataToList(byte[] data) {
// List<Map<String, Object>> dataList = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
// 基础信息
Map<String, Object> baseInfo = new LinkedHashMap<>();
baseInfo.put("dataType", "Linkage_DATA");
int eventType = buffer.getShort() & 0xFFFF;
baseInfo.put("eventType", eventType);
baseInfo.put("eventTypeValue", EventTypeMapper.getEventTypeName(eventType));
baseInfo.put("timestamp", parseBcdTime(buffer));
// baseInfo.put("totalNodes", buffer.getShort() & 0xFFFF);
baseInfo.put("reportedNodes", buffer.get() & 0xFF);
// dataList.add(baseInfo);
System.out.println(buffer.remaining());
// 节点数据
// 节点数据(动态添加数字后缀)
int nodeCounter = 0;
while (buffer.remaining() >= 7) {
String suffix = String.valueOf(nodeCounter++);
// Map<String, Object> nodeMap = new LinkedHashMap<>();
baseInfo.put("nodeId" + suffix, buffer.getShort() & 0xFFFF);
// baseInfo.put("nodeType" + suffix, buffer.get() & 0xFF);
// nodeMap.put("unit", buffer.get() & 0xFF); // 节点节点参数
// nodeMap.put("precision", buffer.get() & 0xFF);
// baseInfo.put("gasType" + suffix, GasDetectorParser.parse(buffer.getShort() & 0xFFFF));
Integer dataValue = buffer.getShort() & 0xFFFF;
// if (Objects.equals(baseInfo.get("nodeType" + suffix), 0)) {
// BigDecimal decimalValue = new BigDecimal(dataValue)
// .divide(new BigDecimal(1), 4, RoundingMode.HALF_UP).stripTrailingZeros();
baseInfo.put("dataValues" + suffix, dataValue);
// 0节点为探测器
// if (Objects.equals(baseInfo.get("nodeType" + suffix), 0)) {
String status = (dataValue == 0) ? "不支持反馈" :
(dataValue == 1) ? "反馈未动作" :
(dataValue == 2) ? "反馈动作" : "未知状态";
baseInfo.put("dataDetectorValue" + suffix, status);
// } else if (Objects.equals(baseInfo.get("nodeType" + suffix), 1)) {
// baseInfo.put("dataValues" + suffix, dataValue);
//1节点为输出模块
// baseInfo.put("dataSinksValue" + suffix, baseInfo.get("dataValues" + suffix).equals("0") ? "未动作" : "已动作");
// }
// dataList.add(nodeMap);
}
return baseInfo;
}
// 辅助方法:字节数组转十六进制字符串
private static String bytesToHex(byte[] bytes) {
StringBuilder hexSb = new StringBuilder();
for (byte b : bytes) {
hexSb.append(String.format("%02X", b));
}
return hexSb.toString();
}
// 保留原有辅助方法
/*public static String parseBcdTime(ByteBuffer buffer) {
// 打印ByteBuffer的十六进制表示
System.out.println("ByteBuffer内容十六进制: " + bytesToHex(buffer.array()));
byte[] bcdTime = new byte[6];
buffer.get(bcdTime);
StringBuilder sb = new StringBuilder();
for (byte b : bcdTime) {
// 提取高四位和低四位,并转换为十进制数字
int high = (b >> 4) & 0x0F;
int low = b & 0x0F;
// 组合成两位十进制字符串
sb.append(String.format("%1d%1d", high, low));
}
String bcdStr = sb.toString();
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
sdf.set2DigitYearStart(new Date(946684800000L)); // 2000-01-01
Date date = sdf.parse(bcdStr);
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
} catch (Exception e) {
return bcdStr; // 返回原始BCD字符串可能包含越界值
}
}*/
public static String parseBcdTime(ByteBuffer buffer) {
// 打印ByteBuffer的十六进制表示
System.out.println("ByteBuffer内容十六进制: " + bytesToHex(buffer.array()));
byte[] bcdTime = new byte[6];
buffer.get(bcdTime);
StringBuilder sb = new StringBuilder();
for (byte b : bcdTime) {
sb.append(String.format("%02X", b));
}
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
sdf.set2DigitYearStart(new SimpleDateFormat("yyyy").parse("2000")); // 设置世纪转折点
Date date = sdf.parse(sb.toString());
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
} catch (Exception e) {
return sb.toString();
}
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
private static boolean isControllerEvent(int eventType) {
return eventType >= 30 && eventType <= 38;
}
}

@ -0,0 +1,30 @@
/*
* +----------------------------------------------------------------------
* | Copyright (c) 2021-2022 All rights reserved.
* +----------------------------------------------------------------------
* | Licensed
* +----------------------------------------------------------------------
* | Author: xw2sy@163.com
* +----------------------------------------------------------------------
*/
package cc.iotkit.plugins.emqx.conf;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "emqx")
public class MqttConfig {
private String host;
private int port;
private boolean ssl;
private String topics;
private int authPort;
}

@ -0,0 +1,105 @@
package cc.iotkit.plugins.emqx.conf;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.vertx.core.json.JsonObject;
import io.vertx.mqtt.messages.MqttMessage;
import io.vertx.mqtt.messages.MqttPublishMessage;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class MqttDataProcessor {
private static final byte HEADER = (byte) 0xAA;
private static final byte FOOTER = (byte) 0x55;
private static final int CRC_START_INDEX = 3; // 帧长度开始位置
private static final String HEX_FORMAT = "0x%02X";
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
public static JsonObject parseFrame(byte[] frameData) {
// 基础校验
if (frameData[0] != HEADER || frameData[frameData.length - 1] != FOOTER) {
return null;
}
// 提取各字段
ByteBuffer buffer = ByteBuffer.wrap(frameData);
byte type = buffer.get(1);
byte version = buffer.get(2);
int length = Short.toUnsignedInt(buffer.getShort(3));
byte mid = buffer.get(5);
byte cmd = buffer.get(6);
String hexCmd = String.format(HEX_FORMAT, cmd);
// 数据域提取
int dataLength = length - 10; // 总长减去固定字段长度
byte[] data = new byte[dataLength];
System.arraycopy(frameData, 7, data, 0, dataLength);
String data1 = parseToHexString(data);
// CRC校验
byte[] crcData = new byte[length - 6]; // 从LENGTH到DATA
System.arraycopy(frameData, 3, crcData, 0, crcData.length);
int receivedCrc = (frameData[length-2] & 0xFF) << 8 | (frameData[length-3] & 0xFF);
int calculatedCrc = calculateModbusCRC(crcData);
if (receivedCrc != calculatedCrc) {
return null;
}
try {
// 转换为16进制字符串
// 完整帧数据转16进制
StringBuilder frameHexBuilder = new StringBuilder();
for (byte b : frameData) {
frameHexBuilder.append(String.format("%02X", b));
}
String rawHex = frameHexBuilder.toString();
// 构建JSON
// ObjectMapper mapper = new ObjectMapper();
// ObjectNode json = mapper.createObjectNode();
JsonObject json = new JsonObject();
json.put("protocolType", type);
json.put("protocolVersion", version);
json.put("messageId", mid);
json.put("cmd", hexCmd);
json.put("data", data1);
json.put("rawHex", rawHex);
json.put("crcValid", true);
System.out.println("拿到的所有数据++++++++++" + json);
// JsonObject jsonObject = new JsonObject(json.toString());
return json;
} catch (Exception e) {
}
return null;
}
public static String parseToHexString(byte[] frameData) {
StringBuilder hexStr = new StringBuilder();
for (byte b : frameData) {
hexStr.append(String.format("%02X ", b));
}
return hexStr.toString().trim();
}
public static int calculateModbusCRC(byte[] data) {
int crc = 0xFFFF;
for (byte b : data) {
crc ^= (b & 0xFF);
for (int i = 0; i < 8; i++) {
if ((crc & 1) == 1) {
crc = (crc >>> 1) ^ 0xA001;
} else {
crc >>>= 1;
}
}
}
return crc;
}
}

@ -0,0 +1,71 @@
package cc.iotkit.plugins.emqx.conf;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class MqttHexProcessor {
// 检测是否为HEX字符串格式
private static boolean isHexString(byte[] data) {
if (data.length % 2 != 0) return false;
for (byte b : data) {
char c = (char) b;
if (!(Character.isDigit(c) ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f') ||
Character.isWhitespace(c))) { // 新增空格检查
return false;
}
}
return true;
}
// HEX字符串转字节数组
/* private static byte[] parseHexString(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}*/
// 统一返回byte[]的入口方法
public static byte[] processToBytes(byte[] payload) {
try {
String payloadStr = new String(payload, StandardCharsets.UTF_8);
System.out.println("payloadStr:" + payloadStr);
System.out.println("payloadStr1:" + isHexString(payload));
return isHexString(payload) ?
Hex.decodeHex(payloadStr.replaceAll("\\s+", "")) :
payload; // 原始字节直接返回
} catch (Exception e) {
return payload; // 异常时返回原始数据
}
}
/*public static void main(String[] args) throws DecoderException {
String input = "AA 01 01 00 35001100013836323537313037393339313237393839383631313235323037303833363037383837000000000111b12255"; // 原始字符串
// byte[] result = convertHexStringToBytes(input);
byte[] bytes = Hex.decodeHex(input);
// 基础校验
// 提取各字段
ByteBuffer buffer = ByteBuffer.wrap(bytes);
byte type = buffer.get(1);
byte version = buffer.get(2);
System.out.println(String.format("%02X", bytes[0]));
System.out.println(type);
System.out.println(version);
}*/
}

@ -0,0 +1,293 @@
package cc.iotkit.plugins.emqx.conf;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cc.iotkit.plugins.emqx.conf.CRC16ModbusUtil.buildFrame;
import static cc.iotkit.plugins.emqx.conf.IoTConfigProtocol.*;
import static cc.iotkit.plugins.emqx.conf.MqttDataProcessor.parseFrame;
public class ProtocolTest {
public static ByteBuffer hexStringToByteBuffer(String hex) {
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i+1), 16));
}
return ByteBuffer.wrap(data);
}
public static byte[] parseFrame1(String hexString) {
if (hexString == null || hexString.length() % 2 != 0) {
throw new IllegalArgumentException("Invalid hex string");
}
byte[] bytes = new byte[hexString.length() / 2];
for (int i = 0; i < hexString.length(); i += 2) {
String byteStr = hexString.substring(i, i + 2);
bytes[i / 2] = (byte) Integer.parseInt(byteStr, 16);
}
return bytes;
}
public static String asciiToHex(String asciiStr) {
StringBuilder hex = new StringBuilder();
for (char ch : asciiStr.toCharArray()) {
hex.append(String.format("%02X ", (int) ch));
}
return hex.toString().trim();
}
public static String hexToAscii(String hexStr) {
StringBuilder output = new StringBuilder();
// 移除所有空格
hexStr = hexStr.replaceAll("\\s", "");
for (int i = 0; i < hexStr.length(); i += 2) {
String str = hexStr.substring(i, i + 2);
output.append((char) Integer.parseInt(str, 16));
}
return output.toString();
}
public static ByteBuffer createBcdTimeBuffer(String bcdTimeStr) {
// 验证输入字符串长度是否为12位(6字节BCD码)
if(bcdTimeStr == null || bcdTimeStr.length() != 12) {
throw new IllegalArgumentException("BCD时间字符串必须是12位数字");
}
byte[] bytes = new byte[6];
for(int i = 0; i < 6; i++) {
// 每两位数字转换为一个BCD字节
String twoDigits = bcdTimeStr.substring(i*2, i*2+2);
bytes[i] = (byte)Integer.parseInt(twoDigits);
}
return ByteBuffer.wrap(bytes);
}
public static String formatHexByte(String input) {
// 移除0x前缀并保留纯十六进制字符
String hex = input.replaceAll("(?i)^0x", "").replaceAll("[^0-9a-fA-F]", "");
// 标准化为2位十六进制不足补零超长截断
return hex.isEmpty() ? "00"
: hex.length() == 1 ? "0" + hex
: hex.substring(hex.length() - 2);
}
public static void extractDetectorData(Map<String, Object> baseInfo) {
for (Map.Entry<String, Object> entry : baseInfo.entrySet()) {
if (entry.getKey().startsWith("dataDetectorValue")) {
String suffix = entry.getKey().substring(17); // 去掉前缀"dataDetectorValue"
Integer value = (Integer) entry.getValue();
// 获取对应节点信息
String nodeIdKey = "nodeId" + suffix;
String gasTypeKey = "gasType" + suffix;
System.out.println("NodeID: " + baseInfo.get(nodeIdKey) +
", GasType: " + baseInfo.get(gasTypeKey) +
", DetectorValue: " + value);
}
}
}
public static String generateWriteCommand(int configId, byte[] data) {
ByteBuffer buf = ByteBuffer.allocate(3 + data.length);
//buf.putShort((short) (5 + data.length)); // 数据长度
// buf.putShort((short) terminalType);
buf.putShort((short) configId);
buf.put((byte) data.length); // 数据长度字节
buf.put(data); // 实际数据
return bytesToHex(buf.array());
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
public static void main(String[] args) {
String dsada=null;
System.out.println(dsada.startsWith("https"));
System.out.println(generateWriteCommand(0x0100,ByteBuffer.allocate(4)
.order(ByteOrder.BIG_ENDIAN)
.putInt(Integer.parseUnsignedInt("00010001", 16)).array()));
System.out.println(ByteBuffer.allocate(2).putShort(Short.parseShort("1")).array());
System.out.println(EventTypeMapper.getEventTypeName(Integer.parseInt("20")));
BigDecimal decimalValue = new BigDecimal("1123.391111111111111")
.divide(new BigDecimal(1), 4, RoundingMode.HALF_UP).stripTrailingZeros();
System.out.println(decimalValue);
Map<String, Object> baseInfo = new HashMap<>();
baseInfo.put("nodeId", 12345);
baseInfo.put("nodeType", 0); // 探测器
baseInfo.put("dataDetectorValue", 0);
baseInfo.put("gasType", "ddddddddddddddddd");
baseInfo.put("dataValue", "ddddddddddaaaa"); // 探测器值
baseInfo.put("nodeId0111", 67890);
baseInfo.put("nodeType0111", 1); // 输出模块
baseInfo.put("dataDetectorValue0111", 0);
baseInfo.put("gasType0111", "dasa12321");
baseInfo.put("dataValue0111", "Ddsa");
extractDetectorData(baseInfo);
System.out.println(formatHexByte("0x014"));
// GasAlarmDataParser.parseBcdTime(createBcdTimeBuffer("250707131758"));
System.out.println(GasAlarmDataParser.parseBcdTime(createBcdTimeBuffer("250703103146")));
//String url = "https://minio.dddd.com/feijialuo/20250702/lQLPKHYULtpjo8HM_Mz8sCPMoZZYPmEbCD1keBQpVgA_252_2521751416904105.png";
try {
String url = "https://minio.dddd.com/feijialuo/20250702/lQLPKHYULtpjo8HM_Mz8sCPMoZZYPmEbCD1keBQpVgA_252_2521751416904105.png";
// 使用lastIndexOf和substring方法截取URL
int startIndex = url.lastIndexOf("/feijialuo/") + "/feijialuo/".length();
String result = url.substring(startIndex);
System.out.println("Extracted File Name: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String extractDateAndFileNameFromURL(String url) throws URISyntaxException {
// 解析URL并提取路径部分
URI uri = new URI(url);
String path = uri.getPath();
// 分割路径为部分数组
String[] parts = path.split("/");
// 遍历路径段,找到日期和文件名
String date = null;
String fileName = null;
for (String part : parts) {
if (part.matches("\\d{8}")) { // 检查是否为8位数字日期格式
date = part;
} else if (fileName == null && !part.isEmpty()) { // 找到第一个非空且非日期的部分作为文件名
fileName = part;
// 由于我们已经找到了文件名,可以跳出循环(假设文件名之后没有其他重要信息)
// 但为了保持代码的通用性,这里不强制跳出循环
}
}
// 拼接日期和文件名
if (date != null && fileName != null) {
return date + "/" + fileName;
} else {
throw new IllegalArgumentException("Invalid URL format: Unable to extract date and file name.");
}
}
/* public static void main(String[] args) {
String mid = "0x-102";
String cmd= "0x-103";
// 获取 mid 的最后两位字符
if (mid.length() >= 2) {
mid = mid.substring(mid.length() - 2);
} else {
// 如果 mid 长度小于 2可以根据需求设置默认值或进行其他处理
// 例如,这里我们设置为空字符串,但实际应用中可能需要更合适的默认值或错误处理
mid = "";
}
System.out.println(mid);
// 获取 cmd 的最后两位字符
if (cmd.length() >= 2) {
cmd = cmd.substring(cmd.length() - 2);
} else {
// 如果 cmd 长度小于 2同样根据需求设置默认值或进行其他处理
// 例如,这里我们设置为空字符串
cmd = "";
}
System.out.println(cmd);
System.out.println(asciiToHex("4141303130313030313330303234303032353036323530363235313034343539353434453535"));
System.out.println(hexToAscii("4141 3031 3031 3030 3133 3030 3234 3030 3235 3036 3235 3036 3235 3131 3035 3233 4234 3344 3535"));
byte[] bcdTime = buildBcdTimeData("2025-06-25 10:36:05");
System.out.println(parseBcdTime(buildBcdTimeData("2025-06-25 10:36:05")));
// 转换为16进制字符串输出
System.out.print("16进制BCD时间: ");
for (byte b : bcdTime) {
System.out.print(String.format("%02X ", b));
}
Map original = new HashMap();
original.put("test","tset");
Map map = original;
map.put("test", "value");
System.out.println(original.get("test")); // 如果输出value说明会影响
String replyMessage = buildFrame("01","01",
"00", "24", "0001002506250624154606");
System.out.println("ddd" + replyMessage);
JsonObject payload = parseFrame(parseFrame1("aa01010023042300000415153839383631313235323037303833363037383837b31055"));
String hexData = "00002615153839383631313235323037303833363037383837";
String data =payload.getString("data").replaceAll("\\s+", ""); ;
System.out.println(data);
IoTConfigProtocol.parseReadResponse(hexData);
// 计算字节长度每2字符=1字节
int byteLength = hexData.length() / 2;
// 转换为2字节16进制大端序
String lengthHex = String.format("%04X", byteLength);
System.out.println("原始数据: " + lengthHex);
System.out.println("字节长度: " + byteLength + " (0x" + lengthHex + ")");
System.out.println("2字节长度值: " + lengthHex.substring(0, 2) + " "+ lengthHex.substring(2));
// System.out.println("Buffer内容(HEX): " + buffer.toString("HEX"));
}*/
/*public static void main(String[] args) {
// 3. SIM卡号读取 (只读)
String readCardCmd = IoTConfigProtocol.generateReadCommand(0x0001,
IoTConfigProtocol.CARD);
System.out.println("读取SIM卡指令: " + readCardCmd);
// 1. IP/域名配置 (TLV格式)
String ip = "192.168.1.100";
byte[] ipData = buildTLVData(ip.getBytes());
String writeIpCmd = IoTConfigProtocol.generateWriteCommand(0x01,
IoTConfigProtocol.IP_REALM, ipData);
System.out.println("写入IP指令: " + writeIpCmd);
// 2. 端口配置 (2字节大端序)
short port = 1883;
byte[] portData = ByteBuffer.allocate(2).putShort(port).array();
String writePortCmd = IoTConfigProtocol.generateWriteCommand(0x01,
IoTConfigProtocol.PORT, portData);
System.out.println("写入端口指令: " + writePortCmd);
// 4. 时间同步设置 (BCD编码)
byte[] timeData = new byte[6];
String currentTime = new SimpleDateFormat("yyMMddHHmmss")
.format(new Date());
for(int i=0; i<6; i++){
int digit = Integer.parseInt(currentTime.substring(i*2, i*2+2));
timeData[i] = (byte)(((digit/10)<<4) | (digit%10));
}
String writeTimeCmd = IoTConfigProtocol.generateWriteCommand(0x01,
IoTConfigProtocol.TIME_SYNC, timeData);
System.out.println("时间同步指令: " + writeTimeCmd);
// 5. 版本号读取 (4字节)
String readVerCmd = IoTConfigProtocol.generateReadCommand(0x01,
IoTConfigProtocol.VERSION);
System.out.println("读取版本指令: " + readVerCmd);
// 6. 信号强度读取 (1字节)
String readSignalCmd = IoTConfigProtocol.generateReadCommand(0x01,
IoTConfigProtocol.SIGNAL);
System.out.println("读取信号指令: " + readSignalCmd);
}*/
// 构建TLV格式数据
}

@ -0,0 +1,27 @@
package cc.iotkit.plugins.emqx.conf;
import java.util.Map;
public class ResponseParserDemo {
public static void main(String[] args) {
// 模拟IP响应数据 000100000E0D3139322E3136382E312E313030
String ipResponse = "0000000E0D3139322E3136382E312E313030";
Map<String, Object> ipResult = IoTConfigProtocol.parseReadResponse(ipResponse);
System.out.println("IP解析结果: " + ipResult.get("value"));
// 模拟端口响应
String portResponse = "000001020383";
Map<String, Object> portResult = IoTConfigProtocol.parseReadResponse(portResponse);
System.out.println("端口解析结果: " + portResult.get("value"));
// 模拟SIM卡响应
String cardResponse = "00001800040015464347413230313233343536373839303132";
Map<String, Object> cardResult = IoTConfigProtocol.parseReadResponse(cardResponse);
System.out.println("SIM卡解析结果: " + cardResult.get("value"));
// 模拟时间响应
String timeResponse = "00000D00250605143000";
Map<String, Object> timeResult = IoTConfigProtocol.parseReadResponse(timeResponse);
System.out.println("时间解析结果: " + timeResult.get("value"));
}
}

@ -0,0 +1,12 @@
package cc.iotkit.plugins.emqx.handler;
import io.vertx.core.json.JsonObject;
/**
* @author sjg
*/
public interface IMsgHandler {
void handle(String topic, JsonObject payload);
}

@ -0,0 +1,175 @@
package cc.iotkit.plugins.emqx.service;
/*
* +----------------------------------------------------------------------
* | Copyright (c) 2021-2022 All rights reserved.
* +----------------------------------------------------------------------
* | Licensed
* +----------------------------------------------------------------------
* | Author: xw2sy@163.com
* +----------------------------------------------------------------------
*/
import cc.iotkit.common.utils.CodecUtil;
import cc.iotkit.common.utils.UniqueIdUtil;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.up.DeviceRegister;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.core.PluginInfo;
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Slf4j
@Service
public class AuthVerticle extends AbstractVerticle {
private HttpServer backendServer;
@Setter
private int port;
@Setter
private String serverPassword;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
@Autowired
private PluginInfo pluginInfo;
@Override
public void start() {
backendServer = vertx.createHttpServer();
//第一步 声明Router&初始化Router
Router backendRouter = Router.router(vertx);
//获取body参数得先添加这句
backendRouter.route().handler(BodyHandler.create());
//第二步 配置Router解析url
backendRouter.route(HttpMethod.POST, "/mqtt/auth").handler(rc -> {
JsonObject json = rc.getBodyAsJson();
log.info("mqtt auth:{}", json);
try {
String clientId = json.getString("clientid");
String username = json.getString("username");
String password = json.getString("password");
//服务端插件连接
if (clientId.equals("server") && serverPassword.equals(password)) {
httpResult(rc.response(), 200);
return;
}
//其它客户端连接
String[] parts = clientId.split("_");
if (parts.length < 3) {
log.error("clientid:{}不正确", clientId);
httpResult(rc.response(), 400);
return;
}
log.info("MQTT client auth,clientId:{},username:{},password:{}",
clientId, username, password);
String productKey = parts[0];
String deviceName = parts[1];
String gwModel = parts[2];
if (!username.equals(deviceName)) {
log.error("username:{}不正确", deviceName);
httpResult(rc.response(), 403);
return;
}
ThingProduct product = thingService.getProduct(productKey);
if (product == null) {
log.error("获取产品信息失败,productKey:{}", productKey);
httpResult(rc.response(), 403);
return;
}
String validPasswd = CodecUtil.md5Str(product.getProductSecret() + clientId);
if (!validPasswd.equalsIgnoreCase(password)) {
log.error("密码验证失败,期望值:{}", validPasswd);
httpResult(rc.response(), 403);
return;
}
//网关设备注册
ActionResult result = thingService.post(
pluginInfo.getPluginId(),
DeviceRegister.builder()
.productKey(productKey)
.deviceName(deviceName)
.model(gwModel)
.version("1.0")
.id(UniqueIdUtil.newRequestId())
.time(System.currentTimeMillis())
.build()
);
if (result.getCode() != 0) {
log.error("设备注册失败:{}", result);
httpResult(rc.response(), 403);
return;
}
Set<String> devices = new HashSet<>();
devices.add(productKey + "," + deviceName);
EmqxPlugin.CLIENT_DEVICE_MAP.putIfAbsent(productKey + deviceName, devices);
httpResult(rc.response(), 200);
} catch (Throwable e) {
httpResult(rc.response(), 500);
log.error("mqtt auth failed", e);
}
});
backendRouter.route(HttpMethod.POST, "/mqtt/acl").handler(rc -> {
String json = rc.getBodyAsString();
log.info("mqtt acl:{}", json);
try {
//将json放到rc中方便后续使用
httpResult(rc.response(), 200);
} catch (Throwable e) {
httpResult(rc.response(), 500);
log.error("mqtt acl failed", e);
}
});
backendServer.requestHandler(backendRouter)
.listen(port, "0.0.0.0")
.onSuccess(s -> {
log.info("auth server start success,port:{}", s.actualPort());
}).onFailure(e -> {
e.printStackTrace();
})
;
}
private void httpResult(HttpServerResponse response, int code) {
response.putHeader("Content-Type", "application/json");
response
.setStatusCode(code);
response
.end("{\"result\": \"" + (code == 200 ? "allow" : "deny") + "\"}");
}
@Override
public void stop() throws Exception {
backendServer.close(voidAsyncResult -> log.info("close emqx auth server..."));
}
}

@ -0,0 +1,741 @@
package cc.iotkit.plugins.emqx.service;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.common.utils.ThreadUtil;
import cc.iotkit.common.utils.UniqueIdUtil;
import cc.iotkit.plugin.core.IPlugin;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.*;
import cc.iotkit.plugin.core.thing.actions.down.DeviceConfig;
import cc.iotkit.plugin.core.thing.actions.up.*;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugins.emqx.conf.*;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.bootstrap.realize.PluginCloseListener;
import com.gitee.starblues.core.PluginCloseType;
import com.gitee.starblues.core.PluginInfo;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.mqtt.MqttClient;
import io.vertx.mqtt.MqttClientOptions;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import static cc.iotkit.plugins.emqx.conf.BCDClockGenerator.formatBCDToHex;
import static cc.iotkit.plugins.emqx.conf.BCDClockGenerator.generateBCDTime;
import static cc.iotkit.plugins.emqx.conf.CRC16ModbusUtil.buildFrame;
import static cc.iotkit.plugins.emqx.conf.MqttDataProcessor.parseFrame;
import static cc.iotkit.plugins.emqx.conf.MqttHexProcessor.processToBytes;
/**
* @author sjg
*/
@Slf4j
@Service
public class EmqxPlugin implements PluginCloseListener, IPlugin, Runnable {
@Autowired
private PluginInfo pluginInfo;
@Autowired
private MqttConfig mqttConfig;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IPluginConfig pluginConfig;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
@Autowired
private AuthVerticle authVerticle;
@Autowired
private MqttDevice mqttDevice;
private final ScheduledThreadPoolExecutor emqxConnectTask = ThreadUtil.newScheduled(1, "emqx_connect");
private Vertx vertx;
private String deployedId;
private MqttClient client;
private boolean mqttConnected = false;
private boolean authServerStarted = false;
private static final Map<String, Boolean> DEVICE_ONLINE = new ConcurrentHashMap<>();
public static final Map<String, Set<String>> CLIENT_DEVICE_MAP = new HashMap<>();
//定义一个线程池线程池大小时cpu的2倍
// 创建线程池大小为CPU核心数的2倍
private static ExecutorService statusThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
// 创建线程池大小为CPU核心数的2倍
private static ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
// 创建线程池大小为CPU核心数的2倍
private static ExecutorService sxsbThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
// 创建线程池大小为CPU核心数的2倍
private static ExecutorService onlineThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
@PostConstruct
public void init() {
vertx = Vertx.vertx();
try {
//获取插件最新配置替换当前配置
Map<String, Object> config = pluginConfig.getConfig(pluginInfo.getPluginId());
BeanUtil.copyProperties(config, mqttConfig, CopyOptions.create().ignoreNullValue());
String serverPassword = IdUtil.fastSimpleUUID();
MqttClientOptions options = new MqttClientOptions()
.setClientId("server112233")
.setUsername("server112233")
.setPassword(serverPassword)
.setCleanSession(true)
.setMaxInflightQueue(100)
.setMaxMessageSize(1024*1024)
.setKeepAliveInterval(60);
if (mqttConfig.isSsl()) {
options.setSsl(true)
.setTrustAll(true);
}
client = MqttClient.create(vertx, options);
mqttDevice.setClient(client);
authVerticle.setPort(mqttConfig.getAuthPort());
authVerticle.setServerPassword(serverPassword);
emqxConnectTask.scheduleWithFixedDelay(this, 3, 3, TimeUnit.SECONDS);
} catch (Throwable e) {
log.error("mqtt plugin startup error", e);
}
}
@Override
public void run() {
if (!authServerStarted) {
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
Future<String> future = vertx.deployVerticle(authVerticle);
future.onSuccess((s -> {
deployedId = s;
countDownLatch.countDown();
authServerStarted = true;
log.info("start emqx auth plugin success");
}));
future.onFailure(e -> {
countDownLatch.countDown();
authServerStarted = false;
log.error("start emqx auth plugin failed", e);
});
countDownLatch.await();
} catch (Exception e) {
authServerStarted = false;
log.error("start emqx auth server failed", e);
}
}
if (mqttConnected) {
return;
}
try {
String[] topics = mqttConfig.getTopics().split(",");
Map<String, Integer> subscribes = new HashMap<>(topics.length);
for (String topic : topics) {
subscribes.put(topic, 1);
}
client.connect(mqttConfig.getPort(), mqttConfig.getHost(), s -> {
if (s.succeeded()) {
log.info("client connect success.");
mqttConnected = true;
client.subscribe(subscribes, e -> {
if (e.succeeded()) {
log.info("===>subscribe success: {}", e.result());
} else {
log.error("===>subscribe fail: ", e.cause());
}
});
} else {
mqttConnected = false;
log.error("client connect fail: ", s.cause());
}
}).publishHandler(msg -> {
// 获取当前线程对象
Thread currentThread = Thread.currentThread();
// 获取当前线程的 ID
long threadId = currentThread.getId();
long start = System.currentTimeMillis() ;
log.info("threadId=189{}",threadId+":"+start);
String topic = msg.topicName();
log.info("topic={}",topic);
if (topic.contains("/request/")) {
return;
}
if (topic != null && topic.startsWith("FIGARO/")){
String s = Arrays.asList(topic.split("/")).get(2);
topic = "FIGARO/post/*/"+s;
// log.info("Client received message on [{}] payload [{}] with QoS [{}]", topic, msg.payload().toJsonObject(), msg.qosLevel());
}
// JsonObject payload =msg.payload().toJsonObject();
// JsonObject payload = parseFrame(msg.payload().getBytes());
JsonObject payload = parseFrame(processToBytes(msg.payload().getBytes()));
System.out.println("payload原始数据++++++++++++++++++++" + payload);
if(ObjectUtil.isEmpty(payload)){
return;
}
try {
//客户端连接断开
if (topic.equals("/sys/client/disconnected")) {
offline(payload.getString("clientid"));
return;
}
ThingDevice device = getDevice(topic);
if (device == null) {
return;
}
long start0 = System.currentTimeMillis() ;
log.info("threadId=222{}",threadId+":"+start0);
//有消息上报-设备上线
online("*", device.getDeviceName());
JsonObject defParams = JsonObject.mapFrom(new HashMap<>(0));
IDeviceAction action = null;
String method = payload.getString("method", "");
if(StringUtils.isBlank(method)){
method = "thing.event.property.post";
}
long start1 = System.currentTimeMillis() ;
log.info("threadId=229{}",threadId+":"+start1);
if (StringUtils.isBlank(method)) {
return;
}
boolean flag = true;
JsonObject params = null;
String replyHex = null;
if(!StringUtils.isBlank(payload.getString("method", ""))){
params = payload.getJsonObject("params", defParams);
}else{
String cmd =payload.getString("cmd");
String data =payload.getString("data").replaceAll("\\s+", ""); ;
String rawHex =payload.getString("rawHex").replaceAll("\\s+", ""); ;
replyHex = replyToInstructions(payload);//回复的数据
Map<String, Object> mapRaw = new HashMap<>();
mapRaw.put("rawHex", rawHex);
mapRaw.put("replyHex", replyHex);
Map<String, Object> result = new HashMap<>(mapRaw);
long start2 = System.currentTimeMillis() ;
log.info("threadId=248{}",threadId+":"+start2);
if(ObjectUtils.isNotEmpty(cmd)&& ObjectUtils.isNotEmpty(data)){
if(cmd.equals("0x11")){
//4.1 状态信息上报命令码0x11
DeviceStatusParser.parseStatusReportToMap(data).forEach(
(key, value) -> result.merge(key, value, (v1, v2) -> v2) // 冲突时保留后者
);
// params= payload.getJsonObject(DeviceStatusParser.parseStatusReportToMap(data),defParams);
payload.put("params",result);
device.setDeviceName(result.get("imei").toString());
switch(result.get("deviceType").toString()) {
case "0":
device.setProductKey("CEMpmANABN7Tt6Jh");
//家用报警器
break;
case "1":
device.setProductKey("XmXYxjzihseT76As");
//独立式报警器
break;
case "2":
device.setProductKey("bAASX8tBjYQjBGFP");
//工商业控制器
break;
case "3":
//动火离人设备
device.setProductKey("WfpZZFkMxxbGfRca");
case "4":
//联动控制箱设备
System.out.println("联动控制箱设备注册++++++++++++++++++++++++++++++++++++++++++");
device.setProductKey("MQFejp7cyDMH3enG");
break;
}
//注册
IDeviceAction action1 = DeviceRegister.builder()
.model("FIGARO")
.id(UUID.randomUUID().toString())
.deviceName(device.getDeviceName())
.productKey(device.getProductKey())
.params(result)
.time(System.currentTimeMillis())
.build();
long start3= System.currentTimeMillis() ;
log.info("threadId=291{}",threadId+":"+start3);
statusThreadPool.submit(new Runnable() {
@Override
public void run() {
thingService.post(pluginInfo.getPluginId(), action1);
}
});
long start4= System.currentTimeMillis() ;
log.info("threadId=299{}",threadId+":"+start4);
}else if(cmd.equals("0x12")){
//4.2 燃气报警器物联网模块数据上报0x12
GasAlarmDataParser.parseHexDataToList(data).forEach(
(key, value) -> result.merge(key, value, (v1, v2) -> v2));
payload.put("params",result);
long start5= System.currentTimeMillis() ;
log.info("threadId=306{}",threadId+":"+start5);
// GasAlarmDataParser.parseHexDataToList(data);
}else if(cmd.equals("0x14")){
//动火离人数据上报与事件上报
FireSafetyDataReceiver.handleHexData(data).forEach(
(key, value) -> result.merge(key, value, (v1, v2) -> v2));
payload.put("params",result);
long start6= System.currentTimeMillis() ;
log.info("threadId=314{}",threadId+":"+start6);
}else if(cmd.equals("0x15")){
//联动控制箱设备状态及参数数据上报
LinkageControlDataParser.parseHexDataToList(data).forEach(
(key, value) -> result.merge(key, value, (v1, v2) -> v2));
payload.put("params",result);
long start7= System.currentTimeMillis() ;
log.info("threadId=321{}",threadId+":"+start7);
}
else if(cmd.equals("0x23")){
flag=false;
IoTConfigProtocol.parseReadResponse(data).forEach(
(key, value) -> result.merge(key, value, (v1, v2) -> v2));
//读取配置数据
payload.put("params",result);
//读取配置
DeviceConfig config = DeviceConfig.builder()
.id(UUID.randomUUID().toString())
.deviceName(device.getDeviceName())
.productKey(device.getProductKey())
.time(System.currentTimeMillis())
.config(payload.getJsonObject("params", defParams).getMap())
.build();
long start8= System.currentTimeMillis() ;
log.info("threadId=339{}",threadId+":"+start8);
threadPool.submit(new Runnable() {
@Override
public void run() {
thingService.post(pluginInfo.getPluginId(), config);
}
});
long start9= System.currentTimeMillis() ;
log.info("threadId=347{}",threadId+":"+start9);
}
else if(cmd.equals("0x24")){
flag=false;
//下发配置数据的回复
// Map<String, Object> mapConfig = FireSafetyDataReceiver.handleHexConfigData(data);
// if(!mapConfig.isEmpty()) {
// payload.put("params", mapConfig);
// }
// long start10= System.currentTimeMillis() ;
// log.info("threadId=357{}",threadId+":"+start10);
}
}
// params =
//解析设备数据
}
params = payload.getJsonObject("params", defParams);
System.out.println("params+++++++++-----------++++++++++++++++++++++++++" + params);
if (ObjectUtil.isNull(params) || params.isEmpty()){
return;
}
// System.out.println(payload.getJsonObject("data", defParams));
if (ObjectUtils.isNotEmpty(method) && "thing.lifetime.register".equalsIgnoreCase(method)) {
//子设备注册
String subPk = params.getString("productKey");
String subDn = params.getString("deviceName");
String subModel = params.getString("model");
long start11= System.currentTimeMillis() ;
log.info("threadId=378{}",threadId+":"+start11);
ActionResult regResult = thingService.post(pluginInfo.getPluginId(),fillAction(
SubDeviceRegister.builder()
.productKey(device.getProductKey())
.deviceName(device.getDeviceName())
.version("1.0")
.subs(List.of(
DeviceRegister.builder()
.productKey(subPk)
.deviceName(subDn)
.model(subModel)
.build()
))
.build()
)
);
long start12= System.currentTimeMillis() ;
log.info("threadId=395{}",threadId+":"+start12);
if (regResult.getCode() == 0) {
//注册成功
reply(topic, payload, 0);
Set<String> devices = CLIENT_DEVICE_MAP.get(device.getProductKey() + device.getDeviceName());
devices.add(subPk + "," + subDn);
} else {
//注册失败
reply(topic, new JsonObject(), regResult.getCode());
}
return;
}
/* - //假设为数组
params.getMap();
if (params.getMap().isEmpty()) {
params = new JsonObject();
}*/
//属性上报
if(StringUtils.isBlank(payload.getString("method", ""))){
action = PropertyReport.builder()
.params(getConfigMap(payload))
.build();
}else{
action = PropertyReport.builder()
.params(params.getMap())
.build();
}
long start13= System.currentTimeMillis() ;
log.info("threadId=424{}",threadId+":"+start13);
try {
if(flag) {
reply1(replyHex,device.getDeviceName(), payload);
}
}catch (Exception e){
log.error("reply error", e);
}
if (action == null) {
return;
}
action.setId(UUID.randomUUID().toString());
action.setProductKey(device.getProductKey());
action.setDeviceName(device.getDeviceName());
action.setTime(System.currentTimeMillis());
long start14= System.currentTimeMillis() ;
log.info("threadId=441{}",threadId+":"+start14);
IDeviceAction finalAction = action;
sxsbThreadPool.submit(new Runnable() {
@Override
public void run() {
thingService.post(pluginInfo.getPluginId(), finalAction);
}
});
long start15= System.currentTimeMillis() ;
log.info("threadId=450{}",threadId+":"+start15);
} catch (Exception e) {
log.error("message is illegal.", e);
}
}).closeHandler(e -> {
mqttConnected = false;
log.info("client closed");
}).exceptionHandler(event -> log.error("client fail", event));
} catch (Exception e) {
log.error("start emqx client failed", e);
}
}
public ThingDevice getDevice(String topic) {
String[] topicParts = topic.split("/");
if (topicParts.length < 4) {
return null;
}
System.out.println(topicParts[2]);
return ThingDevice.builder()
.productKey(topicParts[2])
.deviceName(topicParts[3])
.build();
}
public void online(String pk, String dn) {
/* if (Boolean.TRUE.equals(DEVICE_ONLINE.get(dn))) {
return;
}*/
onlineThreadPool.submit(new Runnable() {
@Override
public void run() {
//上线
thingService.post(
pluginInfo.getPluginId(),
fillAction(DeviceStateChange.builder()
.productKey(pk)
.deviceName(dn)
.state(DeviceState.ONLINE)
.build()
)
);
DEVICE_ONLINE.put(dn, true);
}
});
}
public void offline(String clientId) {
String[] parts = clientId.split("_");
Set<String> devices = CLIENT_DEVICE_MAP.get(parts[0] + parts[1]);
for (String device : devices) {
String[] pkDn = device.split(",");
//下线
thingService.post(
pluginInfo.getPluginId(),
fillAction(DeviceStateChange.builder()
.productKey(pkDn[0])
.deviceName(pkDn[1])
.state(DeviceState.OFFLINE)
.build()
)
);
DEVICE_ONLINE.remove(pkDn[1]);
}
}
private IDeviceAction fillAction(IDeviceAction action) {
action.setId(UniqueIdUtil.newRequestId());
action.setTime(System.currentTimeMillis());
return action;
}
private String replyToInstructions(JsonObject payload) {
String mid = payload.getString("messageId");
String cmd = payload.getString("cmd");
//payload.getString("cmd");
/* System.out.println("mid1" + mid);
System.out.println("cmd1" + cmd);
if (mid.toLowerCase().startsWith("0x")) {
mid = mid.substring(2);
}
if (cmd.toLowerCase().startsWith("0x")) {
cmd = cmd.substring(2);
}
cmd = cmd.replaceAll("\\D", "");
mid = mid.replaceAll("\\D", "");
if (cmd.length() == 0) {
cmd = "00"; // 空值默认补零
} else if (cmd.length() == 1) {
cmd = "0" + cmd; // 单字符左侧补零
} else if (cmd.length() > 2) {
cmd = cmd.slice(-2); // 超长取最后两位
}
if (mid.length() % 2 != 0) {
mid = "0" + mid; // 左侧补零
}
if (cmd.length() % 2 != 0) {
cmd = "0" + cmd; // 左侧补零
}*/
cmd = formatHexByte(cmd);
mid = formatHexByte(mid);
System.out.println("mid" + mid);
System.out.println("cmd" + cmd);
// System.out.println(mid + "re++++++++++++++++++++++++++++++++++++++++++++++++++" + cmd);
// System.out.println("BCD Time: " + formatBCDToHex(generateBCDTime()));
String replyMessage = buildFrame("01", "01",
mid, cmd, "00" + formatBCDToHex(generateBCDTime()));
return replyMessage;
}
public static String formatHexByte(String input) {
// 移除0x前缀并保留纯十六进制字符
String hex = input.replaceAll("(?i)^0x", "").replaceAll("[^0-9a-fA-F]", "");
// 标准化为2位十六进制不足补零超长截断
return hex.isEmpty() ? "00"
: hex.length() == 1 ? "0" + hex
: hex.substring(hex.length() - 2);
}
/**
*
*/
private void reply1(String replyMessage,String deviceName, JsonObject payload) {
Map<String, Object> payloadReply = new HashMap<>();
if(ObjectUtil.isNull(replyMessage)) {
String mid = payload.getString("messageId");
String cmd = payload.getString("cmd");
payload.getString("cmd");
System.out.println("mid1" + mid);
System.out.println("cmd1" + cmd);
if (mid.toLowerCase().startsWith("0x")) {
mid = mid.substring(2);
}
if (cmd.toLowerCase().startsWith("0x")) {
cmd = cmd.substring(2);
}
cmd = cmd.replaceAll("\\D", "");
mid = mid.replaceAll("\\D", "");
if (mid.length() % 2 != 0) {
mid = "0" + mid; // 左侧补零
}
if (cmd.length() % 2 != 0) {
cmd = "0" + cmd; // 左侧补零
}
System.out.println("mid" + mid);
System.out.println("cmd" + cmd);
// System.out.println(mid + "re++++++++++++++++++++++++++++++++++++++++++++++++++" + cmd);
// System.out.println("BCD Time: " + formatBCDToHex(generateBCDTime()));
replyMessage = buildFrame("01", "01",
mid, cmd, "00" + formatBCDToHex(generateBCDTime()));
}
payloadReply.put("data", replyMessage);
String topic="FIGARO/request/" + deviceName;
/* byte[] bytes = replyMessage.getBytes(StandardCharsets.UTF_8);
Buffer buffer = Buffer.buffer(bytes);*/
client.publish(topic, Buffer.buffer(replyMessage), MqttQoS.AT_LEAST_ONCE, false, false)
.onSuccess(h -> {
log.info("publish {} success", topic);
});
/* client.publish(topic, JsonObject.mapFrom(payloadReply).toBuffer(), MqttQoS.AT_LEAST_ONCE, false, false)
.onSuccess(h -> {
log.info("publish {} success", finalTopic);
});*/
}
/**
*
*/
private void reply(String topic, JsonObject payload, int code) {
Map<String, Object> payloadReply = new HashMap<>();
// System.out.println("BCD Time: " + formatBCDToHex(generateBCDTime()));
String replyMessage = buildFrame("01","01",
"00","01","01"+formatBCDToHex(generateBCDTime()));
if( StringUtils.isBlank(payload.getString("method"))){
payloadReply.put("id", payload.getString("messageID"));
payloadReply.put("method", "thing.event.property.post_reply");
payloadReply.put("code", code);
// 先校验数据类型再转换
// 使用Vert.x原生方法避免强制转换
JsonObject params = null;
JsonObject defParams = JsonObject.mapFrom(new HashMap<>(0));
params = payload.getJsonObject("params", defParams);
// JsonArray attribute = payload.getJsonArray("params", new JsonArray()); // 提供默认值:ml-citation{ref="1" data="citationList"}
JsonArray attribute = new JsonArray();
// JsonObject jsonObject = payload.getMap().get("params").getJsonObject("params", defParams);
attribute.add(payload.getMap().get("params")); // 回退处理为单元素数组:ml-citation{ref="8" data="citationList"}
// JsonArray attribute = payload.getJsonArray("params");
List points = attribute.getList();
Map<String, Object> attrMap = (Map<String, Object>) points.get(0);
List<Map<String, Object>>arrList = (List<Map<String, Object>>) attrMap.get("points");
Map<String,Object> map = new HashMap<>();
if(ObjectUtils.isEmpty(arrList)){
payloadReply.put("data", attrMap);
}else{
arrList.forEach(s->{
map.put((String) s.get("name"),s.get("value"));
});
payloadReply.put("data", map);
}
topic = topic.replace("/s/", "/c/") + "_reply";
}else{
payloadReply.put("id", payload.getString("id"));
payloadReply.put("method", payload.getString("method") + "_reply");
payloadReply.put("code", code);
payloadReply.put("data", payload.getJsonObject("params"));
topic = topic.replace("/s/", "/c/") + "_reply";
}
payloadReply.put("data", replyMessage);
String finalTopic = topic;
/* String dadad="FIGARO/request/862571079391279";
byte[] bytes = replyMessage.getBytes(StandardCharsets.UTF_8);
Buffer buffer = Buffer.buffer(bytes);*/
/* client.publish(topic, payloadReply.get(""), MqttQoS.AT_LEAST_ONCE, false, false)
.onSuccess(h -> {
log.info("publish {} success", finalTopic);
});*/
client.publish(topic, JsonObject.mapFrom(payloadReply).toBuffer(), MqttQoS.AT_LEAST_ONCE, false, false)
.onSuccess(h -> {
log.info("publish {} success", finalTopic);
});
}
public Map<String, Object> getConfigMap(JsonObject payload) {
JsonArray attribute = new JsonArray();
attribute.add(payload.getMap().get("params"));
// JsonArray attribute = payload.getJsonArray("params");
List points = attribute.getList();
Map<String, Object> attrMap = (Map<String, Object>) points.get(0);
List<Map<String, Object>>arrList = (List<Map<String, Object>>) attrMap.get("points");
if(ObjectUtils.isEmpty(arrList)){
return attrMap;
}
Map<String,Object> map = new HashMap<>();
/* for (int i = 0; i < arrList.size(); i++) {
map.put((String) arrList.get(i).get("name"),arrList.get(i).get("value"));
}*/
arrList.forEach(s->{
map.put((String) s.get("name"),s.get("value"));
});
return map;
}
@Override
public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) {
try {
log.info("plugin close,type:{},pluginId:{}", closeType, pluginInfo.getPluginId());
if (deployedId != null) {
CountDownLatch wait = new CountDownLatch(1);
Future<Void> future = vertx.undeploy(deployedId);
future.onSuccess(unused -> {
log.info("emqx plugin stopped success");
wait.countDown();
});
future.onFailure(h -> {
log.error("emqx plugin stopped failed", h);
wait.countDown();
});
wait.await(5, TimeUnit.SECONDS);
}
client.disconnect()
.onSuccess(unused -> {
mqttConnected = false;
log.info("stop emqx connect success");
})
.onFailure(unused -> log.error("stop emqx connect failure"));
emqxConnectTask.shutdown();
} catch (Throwable e) {
log.error("emqx plugin stop error", e);
}
}
@Override
public Map<String, Object> getLinkInfo(String pk, String dn) {
return null;
}
}

@ -0,0 +1,70 @@
package cc.iotkit.plugins.emqx.service;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
*
*
* @author sjg
*/
@Slf4j
public class FakeThingService implements IThingService {
/**
*
*/
private static final Map<String, String> PRODUCTS = Map.of(
"hbtgIA0SuVw9lxjB", "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU",
"Rf4QSjbm65X45753", "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU",
"cGCrkK7Ex4FESAwe", "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU"
);
/**
*
*/
private static final Map<String, String> DEVICES = new HashMap<>();
static {
for (int i = 0; i < 10; i++) {
DEVICES.put("TEST:GW:" + StringUtils.leftPad(i + "", 6, "0"), "hbtgIA0SuVw9lxjB");
DEVICES.put("TEST_SW_" + StringUtils.leftPad(i + "", 6, "0"), "Rf4QSjbm65X45753");
DEVICES.put("TEST_SC_" + StringUtils.leftPad(i + "", 6, "0"), "cGCrkK7Ex4FESAwe");
}
}
@Override
public ActionResult post(String pluginId, IDeviceAction action) {
log.info("post action:{}", action);
return ActionResult.builder().code(0).build();
}
@Override
public ThingProduct getProduct(String pk) {
return ThingProduct.builder()
.productKey(pk)
.productSecret(PRODUCTS.get(pk))
.build();
}
@Override
public ThingDevice getDevice(String dn) {
return ThingDevice.builder()
.productKey(DEVICES.get(dn))
.deviceName(dn)
.build();
}
@Override
public Map<String, ?> getProperty(String dn) {
return new HashMap<>(0);
}
}

@ -0,0 +1,340 @@
package cc.iotkit.plugins.emqx.service;
import cc.iotkit.common.enums.ErrCode;
import cc.iotkit.common.exception.BizException;
import cc.iotkit.common.utils.JsonUtils;
import cc.iotkit.plugin.core.thing.IDevice;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.down.DeviceConfig;
import cc.iotkit.plugin.core.thing.actions.down.PropertyGet;
import cc.iotkit.plugin.core.thing.actions.down.PropertySet;
import cc.iotkit.plugin.core.thing.actions.down.ServiceInvoke;
import cc.iotkit.plugins.emqx.conf.IoTConfigProtocol;
import cn.hutool.core.util.ObjectUtil;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.mqtt.MqttClient;
import lombok.Setter;
import org.springframework.stereotype.Service;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cc.iotkit.plugins.emqx.conf.BCDClockGenerator.formatBCDToHex;
import static cc.iotkit.plugins.emqx.conf.BCDClockGenerator.generateBCDTime;
import static cc.iotkit.plugins.emqx.conf.CRC16ModbusUtil.buildFrame;
import static cc.iotkit.plugins.emqx.conf.IoTConfigProtocol.*;
import static cn.hutool.core.util.NumberUtil.parseNumber;
/**
* mqtt
*
* @author sjg
*/
@Service
public class MqttDevice implements IDevice {
@Setter
private MqttClient client;
public List<Map> processConfig(Map<String, Object> config) {
if (config == null || config.isEmpty()) {
throw new IllegalArgumentException("Config map cannot be null or empty");
}
List<Map> list = new ArrayList<>();
for (Map.Entry<String, Object> entry : config.entrySet()) {
Map map = new HashMap();
String key = entry.getKey();
String value = entry.getValue().toString();
Integer configId = null;
try {
byte[] tlvData = null;
switch (key) {
case "ip":
configId = IP_REALM;
tlvData = convertToFixedAscii(value,50);
break;
case "port":
case "keepAlive":
case "reportRateUpper":
configId = (key.equals("port") ? PORT : key.equals("keepAlive") ? KEEP_ALIVE : REPORT_RATE_UPPER);
tlvData = ByteBuffer.allocate(2).putShort(Short.parseShort(value)).array();
// tlvData = buildShortData(parseNumber(value));
break;
case "reportAwaitTime":
case "reportRetryTimes":
configId = (key.equals("reportAwaitTime") ? REPORT_AWAIT_TIME : REPORT_RETRY_TIMES);
tlvData = buildByteData(Integer.valueOf(value));
break;
// case "username":
// case "password":
// tlvData = buildStringData(value.toString(), MAX_AUTH_LENGTH);
// break;
case "publishTopicPrefix":
case "subscribeTopicPrefix":
configId = (key.equals("subscribeTopicPrefix") ? SUBSCRIBE_TOPIC : PUBLISH_TOPIC);
tlvData = buildStringData(value.toString(), 30);
break;
case "timeSyncInternal":
configId = TIME_SYNC;
tlvData = buildBcdTimeData(value);
break;
case "reportConcentrationRange":
configId = REPORT_CONCENTRATION_RANGE;
tlvData = ByteBuffer.allocate(2).putShort(Short.parseShort(value)).array();
break;
case "reportRateLower":
configId = REPORT_RATE_LOWER;
tlvData = ByteBuffer.allocate(2).putShort(Short.parseShort(value)).array();
break;
case "controlOutput":
configId = REPORT_RATE_LinkConstraint;
tlvData = ByteBuffer.allocate(4)
.order(ByteOrder.BIG_ENDIAN)
.putInt(Integer.parseUnsignedInt(value, 16)).array();
break;
case "card":
case "signal":
case "version":
System.out.println(key + " is read-only, skip writing");
continue;
default:
System.out.println("Unsupported config: " + key);
continue;
}
if (ObjectUtil.isNotNull(configId)) {
String command = IoTConfigProtocol.generateWriteCommand(
configId, tlvData);
map.put("data", command);
list.add(map);
System.out.printf("Generated command for %s (ID:0x%04X): %s%n",
key, configId, command);
}
} catch (Exception e) {
System.err.printf("Error processing %s: %s%n", key, e.getMessage());
}
}
return list;
}
public List<Map> processGetConfig(Map<String, Object> config) {
if (config == null || config.isEmpty()) {
throw new IllegalArgumentException("Config map cannot be null or empty");
}
List<Map> list = new ArrayList<>();
for (Map.Entry<String, Object> entry : config.entrySet()) {
Map map = new HashMap();
String key = entry.getKey();
Integer configId = null;
try {
switch (key) {
case "ip":
configId = IP_REALM;
break;
case "port":
configId = PORT;
break;
case "reportAwaitTime":
configId = REPORT_AWAIT_TIME;
break;
case "reportRetryTimes":
configId = REPORT_RETRY_TIMES;
break;
case "card":
configId = CARD;
break;
case "username":
configId = USERNAME;
break;
case "password":
configId = PASSWORD;
break;
case "keepAlive":
configId = KEEP_ALIVE;
break;
case "publishTopicPrefix":
configId = PUBLISH_TOPIC;
break;
case "subscribeTopicPrefix":
configId = SUBSCRIBE_TOPIC;
break;
case "reportRateUpper":
configId = REPORT_RATE_UPPER;
break;
case "reportConcentrationRange":
configId = REPORT_CONCENTRATION_RANGE;
break;
case "reportRateLower":
configId = REPORT_RATE_LOWER;
break;
case "signal":
configId = SIGNAL;
break;
case "timeSyncInternal":
configId = TIME_SYNC;
break;
case "version":
configId = VERSION;
break;
case "controlOutput":
configId = REPORT_RATE_LinkConstraint;
break;
default:
System.out.println("Unsupported config: " + key);
continue;
}
if (ObjectUtil.isNotNull(configId)) {
String command = IoTConfigProtocol.generateReadCommand(
configId);
map.put("data", command);
list.add(map);
System.out.println("ddddeeeeeeeeeeeeeeddddd" + map);
/* System.out.printf("Generated command for %s (ID:0x%04X): %s%n",
key, configId, command);*/
}
} catch (Exception e) {
System.err.printf("Error processing %s: %s%n", key, e.getMessage());
}
}
System.out.println("ddddddddddddddddddddddddddddddddd" + list);
return list;
}
@Override
public ActionResult config(DeviceConfig action) {
String topic = "FIGARO/request/"+ action.getDeviceName();
// Map data = JsonUtils.parseObject(action.getConfig(), Map.class);
/* int type = 0x0000;
switch (action.getProductKey()) {
case "CEMpmANABN7Tt6Jh0":
type = 0x0000;
break;
case "XmXYxjzihseT76As":
type = 0x0001;
break;
case "bAASX8tBjYQjBGFP":
type = 0x0002;
break;
case "WfpZZFkMxxbGfRca":
type = 0x0003;
break;
}*/
System.out.println("action.getProductKey()++++++++++++++++++++" + action.getProductKey());
if (action.getModule().equals("set")) {
List<Map> list = processConfig(action.getConfig());
System.out.println("listset++++++++++++++++++++" + list);
for (int i = 0; i < list.size(); i++) {
String replyMessage = buildFrame("01","01",
"00", "24", list.get(i).get("data").toString());
//循环发送配置指令
/* String replyMessage = buildFrame("01", "01",
"01", "24", "01" + list.get(i).get("data"));*/
System.out.println("配置指令" + replyMessage);
send(
topic,replyMessage
);
try {
Thread.sleep(500);
}catch (Exception e){
}
}
} else if(action.getModule().equals("get")){
System.out.println("action.getConfig()++++++++++++++++++++" + action.getConfig());
//配置获取指令下发
List<Map> list = processGetConfig(action.getConfig());
System.out.println("list++++++++++++++++++++" + list);
for (int i = 0; i < list.size(); i++) {
//循环发送配置指令
/* String replyMessage = buildFrame("01", "01",
"01", "23", "01" + list.get(i).get("data"));*/
String replyMessage = buildFrame("01","01",
"00", "23",list.get(i).get("data").toString());
send(
topic,replyMessage
);
try {
Thread.sleep(500);
}catch (Exception e){
}
}
}
return ActionResult.builder().code(0).reason("").build();
}
@Override
public ActionResult propertyGet(PropertyGet action) {
String topic = String.format("/sys/%s/%s/c/service/property/get", action.getProductKey(), action.getDeviceName());
return send(
topic,
new JsonObject()
.put("id", action.getId())
.put("method", "thing.service.property.get")
.put("params", action.getKeys())
);
}
@Override
public ActionResult propertySet(PropertySet action) {
String topic = String.format("/sys/%s/%s/c/service/property/set", action.getProductKey(), action.getDeviceName());
return send(
topic,
new JsonObject()
.put("id", action.getId())
.put("method", "thing.service.property.set")
.put("params", action.getParams())
);
}
@Override
public ActionResult serviceInvoke(ServiceInvoke action) {
String topic = String.format("/sys/%s/%s/c/service/%s", action.getProductKey(), action.getDeviceName(), action.getName());
return send(
topic,
new JsonObject()
.put("id", action.getId())
.put("method", "thing.service." + action.getName())
.put("params", action.getParams())
);
}
private ActionResult send(String topic, JsonObject payload) {
try {
client.publish(topic, payload.toBuffer(), MqttQoS.AT_LEAST_ONCE, false, false);
return ActionResult.builder().code(0).reason("").build();
} catch (BizException e) {
return ActionResult.builder().code(e.getCode()).reason(e.getMessage()).build();
} catch (Exception e) {
return ActionResult.builder().code(ErrCode.UNKNOWN_EXCEPTION.getKey()).reason(e.getMessage()).build();
}
}
private ActionResult send(String topic, String payload) {
try {
/* byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);
Buffer buffer = Buffer.buffer(bytes);*/
client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false);
return ActionResult.builder().code(0).reason("").build();
} catch (BizException e) {
return ActionResult.builder().code(e.getCode()).reason(e.getMessage()).build();
} catch (Exception e) {
return ActionResult.builder().code(ErrCode.UNKNOWN_EXCEPTION.getKey()).reason(e.getMessage()).build();
}
}
}

@ -0,0 +1,9 @@
plugin:
runMode: dev
mainPackage: cc.iotkit.plugin
emqx:
host: 123.57.78.108
port: 1883
topics: FIGARO/#
authPort: 8104

@ -0,0 +1,30 @@
[
{
"id": "host",
"name": "emqx ip",
"type": "text",
"value": "127.0.0.1",
"desc": "emqx ip默认为127.0.0.1"
},
{
"id": "port",
"name": "emqx端口",
"type": "number",
"value": 1883,
"desc": "emqx端口默认为1883"
},
{
"id": "auth_port",
"name": "认证端口",
"type": "number",
"value": 8104,
"desc": "emqx http认证端口默认为8104"
},
{
"id": "topics",
"name": "订阅主题",
"type": "text",
"value": "/sys/#",
"desc": "订阅主题多个用,隔开"
}
]

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>iot-iita-plugins</artifactId>
<groupId>cc.iotkit.plugins</groupId>
<version>2.0.19</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>http-plugin</artifactId>
<version>1.0.1</version>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>${vertx.version}</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<plugin.build.mode>dev</plugin.build.mode>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<plugin.build.mode>prod</plugin.build.mode>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>${spring-brick.version}</version>
<configuration>
<mode>${plugin.build.mode}</mode>
<pluginInfo>
<id>http-plugin</id>
<bootstrapClass>cc.iotkit.plugins.http.Application</bootstrapClass>
<version>${project.version}</version>
<provider>iita</provider>
<description>http示例插件配置参数端口(port)默认9081</description>
<configFileName>application.yml</configFileName>
</pluginInfo>
<prodConfig>
<packageType>jar</packageType>
</prodConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,19 @@
package cc.iotkit.plugins.http;
import com.gitee.starblues.bootstrap.SpringPluginBootstrap;
import com.gitee.starblues.bootstrap.annotation.OneselfConfig;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author sjg
*/
@SpringBootApplication(scanBasePackages = "cc.iotkit.plugins.http")
@OneselfConfig(mainConfigFileName = {"application.yml"})
@EnableConfigurationProperties
public class Application extends SpringPluginBootstrap {
public static void main(String[] args) {
new Application().run(Application.class, args);
}
}

@ -0,0 +1,29 @@
package cc.iotkit.plugins.http.conf;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.LocalPluginConfig;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugins.http.service.FakeThingService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @author sjg
*/
@Component
public class BeanConfig {
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IThingService getThingService() {
return new FakeThingService();
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginConfig getPluginConfig(){
return new LocalPluginConfig();
}
}

@ -0,0 +1,28 @@
/*
* +----------------------------------------------------------------------
* | Copyright (c) 2021-2022 All rights reserved.
* +----------------------------------------------------------------------
* | Licensed
* +----------------------------------------------------------------------
* | Author: xw2sy@163.com
* +----------------------------------------------------------------------
*/
package cc.iotkit.plugins.http.conf;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* http
*
* @author sjg
*/
@Data
@Component
@ConfigurationProperties(prefix = "http")
public class HttpConfig {
private int port;
}

@ -0,0 +1,48 @@
package cc.iotkit.plugins.http.service;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import io.vertx.core.json.JsonObject;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
*
*
* @author sjg
*/
@Slf4j
public class FakeThingService implements IThingService {
@Override
public ActionResult post(String pluginId, IDeviceAction action) {
log.info("post action:{}", action);
return ActionResult.builder().code(0).build();
}
@Override
public ThingProduct getProduct(String pk) {
return ThingProduct.builder()
.productKey("cGCrkK7Ex4FESAwe")
.productSecret("xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU")
.build();
}
@Override
public ThingDevice getDevice(String dn) {
return ThingDevice.builder()
.productKey("cGCrkK7Ex4FESAwe")
.deviceName(dn)
.secret("mBCr3TKstTj2KeM6")
.build();
}
@Override
public Map<String, ?> getProperty(String dn) {
return new JsonObject().put("powerstate", 1).getMap();
}
}

@ -0,0 +1,43 @@
package cc.iotkit.plugins.http.service;
import cc.iotkit.plugin.core.thing.IDevice;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.down.DeviceConfig;
import cc.iotkit.plugin.core.thing.actions.down.PropertyGet;
import cc.iotkit.plugin.core.thing.actions.down.PropertySet;
import cc.iotkit.plugin.core.thing.actions.down.ServiceInvoke;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* http
*
* @author sjg
*/
@Service
public class HttpDevice implements IDevice {
@Autowired
private HttpVerticle httpVerticle;
@Override
public ActionResult config(DeviceConfig action) {
return ActionResult.builder().code(0).reason("").build();
}
@Override
public ActionResult propertyGet(PropertyGet action) {
throw new UnsupportedOperationException("不支持该功能");
}
@Override
public ActionResult propertySet(PropertySet action) {
throw new UnsupportedOperationException("不支持该功能");
}
@Override
public ActionResult serviceInvoke(ServiceInvoke action) {
throw new UnsupportedOperationException("不支持该功能");
}
}

@ -0,0 +1,91 @@
package cc.iotkit.plugins.http.service;
import cc.iotkit.common.utils.JsonUtils;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugins.http.conf.HttpConfig;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.bootstrap.realize.PluginCloseListener;
import com.gitee.starblues.core.PluginCloseType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @author sjg
*/
@Slf4j
@Service
public class HttpPlugin implements PluginCloseListener {
@Autowired
private PluginInfo pluginInfo;
@Autowired
private HttpVerticle httpVerticle;
@Autowired
private HttpConfig httpConfig;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IPluginConfig pluginConfig;
private Vertx vertx;
private String deployedId;
@PostConstruct
public void init() {
vertx = Vertx.vertx();
try {
//获取插件最新配置替换当前配置
Map<String, Object> config = pluginConfig.getConfig(pluginInfo.getPluginId());
log.info("get config:{}", JsonUtils.toJsonString(config));
BeanUtil.copyProperties(config, httpConfig, CopyOptions.create().ignoreNullValue());
httpVerticle.setConfig(httpConfig);
Future<String> future = vertx.deployVerticle(httpVerticle);
future.onSuccess((s -> {
deployedId = s;
log.info("http plugin startup success");
}));
future.onFailure((e) -> {
log.error("http plugin startup failed", e);
});
} catch (Throwable e) {
log.error("http plugin startup error", e);
}
}
@Override
public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) {
try {
log.info("plugin close,type:{},pluginId:{}", closeType, pluginInfo.getPluginId());
if (deployedId != null) {
CountDownLatch wait = new CountDownLatch(1);
Future<Void> future = vertx.undeploy(deployedId);
future.onSuccess(unused -> {
log.info("http plugin stopped success");
wait.countDown();
});
future.onFailure(h -> {
log.info("tcp plugin stopped failed");
h.printStackTrace();
wait.countDown();
});
wait.await(5, TimeUnit.SECONDS);
}
} catch (Throwable e) {
log.error("http plugin stop error", e);
}
}
}

@ -0,0 +1,206 @@
/*
* +----------------------------------------------------------------------
* | Copyright (c) 2021-2022 All rights reserved.
* +----------------------------------------------------------------------
* | Licensed
* +----------------------------------------------------------------------
* | Author: xw2sy@163.com
* +----------------------------------------------------------------------
*/
package cc.iotkit.plugins.http.service;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.DeviceState;
import cc.iotkit.plugin.core.thing.actions.EventLevel;
import cc.iotkit.plugin.core.thing.actions.up.DeviceStateChange;
import cc.iotkit.plugin.core.thing.actions.up.EventReport;
import cc.iotkit.plugin.core.thing.actions.up.PropertyReport;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugins.http.conf.HttpConfig;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* mqtt
* http://iotkit-open-source.gitee.io/document/pages/device_protocol/http/#%E4%BA%8B%E4%BB%B6%E4%B8%8A%E6%8A%A5
*
* @author sjg
*/
@Slf4j
@Component
@Data
public class HttpVerticle extends AbstractVerticle implements Handler<RoutingContext> {
private HttpConfig config;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
@Autowired
private PluginInfo pluginInfo;
private static final Set<String> DEVICE_ONLINE = new HashSet<>();
private HttpServer httpServer;
@Override
public void start() {
Executors.newSingleThreadScheduledExecutor().schedule(this::initHttpServer, 3, TimeUnit.SECONDS);
}
private void initHttpServer() {
httpServer = vertx.createHttpServer();
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create()).handler(this);
httpServer.requestHandler(router).listen(config.getPort(), ar -> {
if (ar.succeeded()) {
log.info("http server is listening on port " + ar.result().actualPort());
} else {
log.error("Error on starting the server", ar.cause());
}
});
}
@Override
public void stop() {
httpServer.close(rst -> {
log.info("http server close:{}", rst.succeeded());
});
}
@Override
public void handle(RoutingContext ctx) {
HttpServerResponse response = ctx.response();
response.putHeader("content-type", "application/json");
response.setStatusCode(200);
try {
String secret = ctx.request().getHeader("secret");
if (StringUtils.isBlank(secret)) {
log.error("secret不能为空");
response.setStatusCode(401);
end(response);
return;
}
HttpServerRequest request = ctx.request();
// /sys/{productKey}/{deviceName}/properties
String path = request.path();
String[] parts = path.split("/");
if (parts.length < 5) {
log.error("不正确的路径");
response.setStatusCode(500);
}
String productKey = parts[2];
String deviceName = parts[3];
String type = parts[4];
ThingDevice device = thingService.getDevice(deviceName);
if (device == null) {
log.error("认证失败,设备:{} 不存在", deviceName);
response.setStatusCode(401);
end(response);
return;
}
if (!secret.equalsIgnoreCase(device.getSecret())) {
log.error("认证失败secret不正确期望值:{}", device.getSecret());
response.setStatusCode(401);
end(response);
return;
}
//设备上线
if (!DEVICE_ONLINE.contains(deviceName)) {
thingService.post(pluginInfo.getPluginId(), DeviceStateChange.builder()
.id(UUID.randomUUID().toString())
.productKey(productKey)
.deviceName(deviceName)
.state(DeviceState.ONLINE)
.time(System.currentTimeMillis())
.build());
DEVICE_ONLINE.add(deviceName);
}
String method = request.method().name();
JsonObject payload = ctx.getBodyAsJson();
if ("event".equals(type)) {
//事件上报
if (!"POST".equalsIgnoreCase(method)) {
response.setStatusCode(500);
log.error("请求类型不正确,期望值:POST实际值:{}", method);
end(response);
}
thingService.post(
pluginInfo.getPluginId(),
EventReport.builder()
.id(payload.getString("id"))
.productKey(productKey)
.deviceName(deviceName)
.level(EventLevel.INFO)
.name(parts[5])
.params(payload.getJsonObject("params").getMap())
.time(System.currentTimeMillis())
.build()
);
end(response);
return;
}
if ("properties".equals(type)) {
if ("POST".equalsIgnoreCase(method)) {
//属性上报
thingService.post(
pluginInfo.getPluginId(),
PropertyReport.builder()
.id(UUID.randomUUID().toString())
.productKey(productKey)
.deviceName(deviceName)
.params(payload.getJsonObject("params").getMap())
.time(System.currentTimeMillis())
.build()
);
end(response);
return;
}
if ("GET".equalsIgnoreCase(method)) {
//属性获取
Map<String, ?> property = thingService.getProperty(deviceName);
response.end(new JsonObject()
.put("code", 0)
.put("data", property)
.toString());
}
}
} catch (Exception e) {
log.error("消息处理失败", e);
response.setStatusCode(500);
end(response);
}
}
private void end(HttpServerResponse response) {
response.end(new JsonObject()
.put("code", response.getStatusCode() == 200 ? 0 : response.getStatusCode())
.toString());
}
}

@ -0,0 +1,6 @@
plugin:
runMode: prod
mainPackage: cc.iotkit.plugin
http:
port: 9081

@ -0,0 +1,26 @@
[
{
"id": "port",
"name": "端口",
"type": "number",
"value": 9081,
"desc": "http端口默认为9081"
},
{
"id": "a",
"name": "测试参数1",
"type": "radio",
"value": 0,
"desc": "单选参数a",
"options": [
{
"name": "值0",
"value": 0
},
{
"name": "值1",
"value": 11
}
]
}
]

@ -0,0 +1,36 @@
package cc.iotkit.test.http;
import cc.iotkit.common.utils.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import io.vertx.core.json.JsonObject;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
public class HttpTest {
public static void main(String[] args) {
ScheduledThreadPoolExecutor timer = ThreadUtil.newScheduled(1, "http-test");
timer.scheduleWithFixedDelay(HttpTest::report, 0, 3, TimeUnit.SECONDS);
}
public static void report() {
HttpResponse rst = HttpUtil.createPost("http://127.0.0.1:9084/sys/cGCrkK7Ex4FESAwe/cz00001/properties")
.header("secret", "mBCr3TKstTj2KeM6")
.body(new JsonObject()
.put("id", IdUtil.fastSimpleUUID())
.put("params", new JsonObject()
.put("powerstate", RandomUtil.randomInt(0, 2))
.put("rssi", RandomUtil.randomInt(-127, 127))
.getMap()
).encode()
).execute();
log.info("send result:status={},body={}", rst.getStatus(), rst.body());
}
}

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>iot-iita-plugins</artifactId>
<groupId>cc.iotkit.plugins</groupId>
<version>2.0.19</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hydrovalve-plugin</artifactId>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<plugin.build.mode>dev</plugin.build.mode>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<plugin.build.mode>prod</plugin.build.mode>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>${spring-brick.version}</version>
<configuration>
<mode>${plugin.build.mode}</mode>
<pluginInfo>
<id>hydrovalve-plugin</id>
<bootstrapClass>cc.iotkit.plugins.hydrovalve.Application</bootstrapClass>
<version>${project.version}</version>
<provider>iita</provider>
<description>modbus插件</description>
<configFileName>application.yml</configFileName>
</pluginInfo>
<prodConfig>
<packageType>jar</packageType>
</prodConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,22 @@
package cc.iotkit.plugins.hydrovalve;
import com.gitee.starblues.bootstrap.SpringPluginBootstrap;
import com.gitee.starblues.bootstrap.annotation.OneselfConfig;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @Authortfd
* @Date2024/1/8 14:57
*/
@SpringBootApplication(scanBasePackages = "cc.iotkit.plugins.hydrovalve")
@OneselfConfig(mainConfigFileName = {"application.yml"})
@EnableConfigurationProperties
@EnableScheduling
public class Application extends SpringPluginBootstrap {
public static void main(String[] args) {
new Application().run(Application.class, args);
}
}

@ -0,0 +1,23 @@
package cc.iotkit.plugins.hydrovalve.analysis;
/**
* @Authortfd
* @Date2024/1/9 15:41
*/
public abstract class ModBusAnalysis {
/**
*
*
* @param entity
* @return
*/
public abstract byte[] packCmd4Entity(ModBusEntity entity);
/**
*
*
* @param arrCmd
* @return
*/
public abstract ModBusEntity unPackCmd2Entity(byte[] arrCmd);
}

@ -0,0 +1,34 @@
package cc.iotkit.plugins.hydrovalve.analysis;
/**
* @Authortfd
* @Date2024/1/9 15:45
*/
public class ModBusConstants {
public static final String MODE = "modbusMode";
public static final String MODE_ASCII = "ASCII";
public static final String MODE_RTU = "RTU";
public static final String MODE_TCP = "TCP";
/**
* ModBus
*/
public static final String SN = "sn";
public static final String ADDR = "devAddr";
public static final String FUNC = "func";
public static final String DATA = "data";
/**
*
*/
public static final String REG_ADDR = "regAddr";
public static final String REG_CNT = "regCnt";
public static final String REG_HOLD_STATUS = "regHoldStatus";
//读多个保持寄存器
public static final byte FUN_CODE3 = 0x03;
//写单个保持寄存器
public static final byte FUN_CODE6 = 0x06;
}

@ -0,0 +1,42 @@
package cc.iotkit.plugins.hydrovalve.analysis;
import lombok.Getter;
import lombok.Setter;
/**
* @Authortfd
* @Date2024/1/9 15:44
*/
@Getter
@Setter
public class ModBusEntity {
/**
*
*/
private int sn = 0;
/**
*
*/
private byte devAddr = 0x01;
/**
*
*/
private byte func = 0x01;
/**
*
*/
private byte[] data = new byte[0];
/**
*
*/
private int errCode = 0;
/**
*
*/
private String errMsg = "";
}

@ -0,0 +1,50 @@
package cc.iotkit.plugins.hydrovalve.analysis;
/**
* @Authortfd
* @Date2024/1/9 15:48
*/
public class ModBusError {
static final String err01 = "err=01:非法的功能码";
static final String err02 = "err=02:非法的数据地址";
static final String err03 = "err=03:非法的数据值";
static final String err04 = "err=04:服务器故障";
static final String err05 = "err=05:确认。";
static final String err06 = "err=06:服务器繁忙";
static final String err10 = "err=10:网关故障:网关路经是无效的";
static final String err11 = "err=11:网关故障:目标设备没有响应";
/**
*
* @param code
* @return
*/
static String getError(int code) {
if (code == 1) {
return err01;
}
if (code == 2) {
return err02;
}
if (code == 3) {
return err03;
}
if (code == 4) {
return err04;
}
if (code == 5) {
return err05;
}
if (code == 6) {
return err06;
}
if (code == 10) {
return err10;
}
if (code == 11) {
return err11;
}
return "";
}
}

@ -0,0 +1,171 @@
package cc.iotkit.plugins.hydrovalve.analysis;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.plugins.hydrovalve.utils.ByteUtils;
import lombok.extern.slf4j.Slf4j;
/**
* @Authortfd
* @Date2024/1/9 15:53
*/
@Slf4j
public class ModBusRtuAnalysis extends ModBusAnalysis {
/**
* CRC16
*
* @param arrCmd
* @return
*/
public static int getCRC16(byte[] arrCmd) {
int iSize = arrCmd.length - 2;
// 检查:帧长度
if (iSize < 2) {
return 0;
}
int wCrcMathematics = 0xA001;
int usCrc16 = 0x00;
//16位的CRC寄存器
int byteCrc16Lo = 0xFF;
int byteCrc16Hi = 0xFF;
//临时变量
int byteSaveHi = 0x00;
int byteSaveLo = 0x00;
//CRC多项式码的寄存器
int byteCl = wCrcMathematics % 0x100;
int byteCh = wCrcMathematics / 0x100;
for (int i = 0; i < iSize; i++) {
byteCrc16Lo &= 0xFF;
byteCrc16Hi &= 0xFF;
byteSaveHi &= 0xFF;
byteSaveLo &= 0xFF;
byteCrc16Lo ^= arrCmd[i]; //每一个数据与CRC寄存器进行异或
for (int k = 0; k < 8; k++) {
byteCrc16Lo &= 0xFF;
byteCrc16Hi &= 0xFF;
byteSaveHi = byteCrc16Hi;
byteSaveLo = byteCrc16Lo;
byteCrc16Hi /= 2; //高位右移一位
byteCrc16Lo /= 2; //低位右移一位
if ((byteSaveHi & 0x01) == 0x01) //如果高位字节最后一位为1
{
byteCrc16Lo |= 0x80; //则低位字节右移后前面补1
} //否则自动补0
if ((byteSaveLo & 0x01) == 0x01) //如果高位字节最后一位为1则与多项式码进行异或
{
byteCrc16Hi ^= byteCh;
byteCrc16Lo ^= byteCl;
}
}
}
usCrc16 = (byteCrc16Hi & 0xff) * 0x100 + (byteCrc16Lo & 0xff);
return usCrc16;
}
/**
*
*
* @param arrCmd
* @return
*/
@Override
public ModBusEntity unPackCmd2Entity(byte[] arrCmd) {
ModBusEntity entity = new ModBusEntity();
int iSize = arrCmd.length;
if (iSize < 4) {
return null;
}
// 地址码
byte byAddr = arrCmd[0];
entity.setDevAddr(byAddr);
// 功能码
byte byFun = arrCmd[1];
entity.setFunc(byFun);
// 数据域
int iDataSize = iSize - 4;
entity.setData(new byte[iDataSize]);
byte[] arrData = entity.getData();
System.arraycopy(arrCmd, 2, arrData, 0, iDataSize);
// 校验CRC
int wCrc16OK = getCRC16(arrCmd);
byte crcH = (byte) (wCrc16OK & 0xff);
byte crcL = (byte) ((wCrc16OK & 0xff00) >> 8);
if (arrCmd[arrCmd.length - 1] == crcL && arrCmd[arrCmd.length - 2] == crcH) {
return entity;
}
return null;
}
/**
*
*
* @return
*/
@Override
public byte[] packCmd4Entity(ModBusEntity entity) {
int iSize = entity.getData().length;
byte[] arrCmd = new byte[iSize + 4];
// 地址码
arrCmd[0] = entity.getDevAddr();
// 功能码
arrCmd[1] = entity.getFunc();
// 数据域
System.arraycopy(entity.getData(), 0, arrCmd, 2, iSize);
// 校验CRC
int wCrc16 = getCRC16(arrCmd);
arrCmd[arrCmd.length - 2] = (byte) (wCrc16 % 0x100);
arrCmd[arrCmd.length - 1] = (byte) (wCrc16 / 0x100);
return arrCmd;
}
public static void main(String[] args) {
// String hexString = "0103020457FB7A";
String hexString = "01060001000119CA";
ModBusRtuAnalysis a=new ModBusRtuAnalysis();
ModBusEntity b=a.unPackCmd2Entity(ByteUtils.hexStrToBinaryStr(hexString));
int lenth=b.getData()[0];
byte[] val = new byte[lenth];
System.arraycopy(b.getData(), 1, val, 0, lenth);
log.info("ret:"+Integer.parseInt(ByteUtils.BinaryToHexString(val,false), 16));
ModBusEntity c=new ModBusEntity();
c.setDevAddr((byte) 1);
c.setFunc(ModBusConstants.FUN_CODE3);
Integer dz=0;
Integer dzsl=10;
String a1=StringUtils.leftPad(dz.toHexString(dz),4,'0')+StringUtils.leftPad(dz.toHexString(dzsl),4,'0');
c.setData(ByteUtils.hexStrToBinaryStr(a1));
byte[] d=a.packCmd4Entity(c);
log.info("ret1:"+ByteUtils.BinaryToHexString(d,false));
ModBusEntity e=new ModBusEntity();
e.setDevAddr((byte) 1);
e.setFunc(ModBusConstants.FUN_CODE6);
Integer dze=0;
Integer dzsle=1;
String a1e=StringUtils.leftPad(dz.toHexString(dze),4,'0')+StringUtils.leftPad(dz.toHexString(dzsle),4,'0');
e.setData(ByteUtils.hexStrToBinaryStr(a1e));
byte[] f=a.packCmd4Entity(e);
log.info("rete:"+ByteUtils.BinaryToHexString(f,false));
}
}

@ -0,0 +1,37 @@
package cc.iotkit.plugins.hydrovalve.conf;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.IPluginScript;
import cc.iotkit.plugin.core.LocalPluginConfig;
import cc.iotkit.plugin.core.LocalPluginScript;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugins.hydrovalve.service.FakeThingService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @Authortfd
* @Date2024/1/8 14:58
*/
@Component
public class BeanConfig {
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IThingService getThingService() {
return new FakeThingService();
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginScript getPluginScript() {
return new LocalPluginScript("script.js");
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginConfig getPluginConfig(){
return new LocalPluginConfig();
}
}

@ -0,0 +1,21 @@
package cc.iotkit.plugins.hydrovalve.conf;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Authortfd
* @Date2024/1/8 15:04
*/
@Data
@Component
@ConfigurationProperties(prefix = "hydrovalve")
public class HydrovalveConfig {
private String host;
private int port;
private int interval;
}

@ -0,0 +1,46 @@
package cc.iotkit.plugins.hydrovalve.service;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
/**
* @Authortfd
* @Date2024/1/8 14:58
*/
@Slf4j
public class FakeThingService implements IThingService {
@Override
public ActionResult post(String pluginId, IDeviceAction action) {
log.info("post action:{}", action);
return ActionResult.builder().code(0).build();
}
@Override
public ThingProduct getProduct(String pk) {
return ThingProduct.builder()
.productKey("cGCrkK7Ex4FESAwe")
.productSecret("aaaaaaaa")
.build();
}
@Override
public ThingDevice getDevice(String dn) {
return ThingDevice.builder()
.productKey("cGCrkK7Ex4FESAwe")
.deviceName(dn)
.build();
}
@Override
public Map<String, ?> getProperty(String dn) {
return new HashMap<>(0);
}
}

@ -0,0 +1,62 @@
package cc.iotkit.plugins.hydrovalve.service;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.plugin.core.thing.IDevice;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.down.DeviceConfig;
import cc.iotkit.plugin.core.thing.actions.down.PropertyGet;
import cc.iotkit.plugin.core.thing.actions.down.PropertySet;
import cc.iotkit.plugin.core.thing.actions.down.ServiceInvoke;
import cc.iotkit.plugins.hydrovalve.analysis.ModBusConstants;
import cc.iotkit.plugins.hydrovalve.analysis.ModBusEntity;
import cc.iotkit.plugins.hydrovalve.analysis.ModBusRtuAnalysis;
import cc.iotkit.plugins.hydrovalve.utils.ByteUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @Authortfd
* @Date2024/1/10 11:06
*/
@Service
public class ModBusDevice implements IDevice {
@Autowired
private ModbusVerticle modbusVerticle;
ModBusRtuAnalysis analysis=new ModBusRtuAnalysis();
@Override
public ActionResult config(DeviceConfig action) {
return ActionResult.builder().code(0).reason("").build();
}
@Override
public ActionResult propertyGet(PropertyGet action) {
return null;
}
@Override
public ActionResult propertySet(PropertySet action) {
ModBusEntity read=new ModBusEntity();
String devAddr=action.getDeviceName().split("_")[1];
read.setFunc(ModBusConstants.FUN_CODE6);
read.setDevAddr(Byte.parseByte(devAddr));
Integer addr=0;
for (Map.Entry<String, ?> entry : action.getParams().entrySet()) {
int val = Integer.parseInt((String) entry.getValue());
String a1= StringUtils.leftPad(addr.toHexString(addr),4,'0')+StringUtils.leftPad(addr.toHexString(val),4,'0');
read.setData(ByteUtils.hexStrToBinaryStr(a1));
byte[] msg = analysis.packCmd4Entity(read);
modbusVerticle.sendMsg(msg);
}
return ActionResult.builder().code(0).reason("success").build();
}
@Override
public ActionResult serviceInvoke(ServiceInvoke action) {
return null;
}
}

@ -0,0 +1,99 @@
package cc.iotkit.plugins.hydrovalve.service;
import cc.iotkit.common.utils.JsonUtils;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.IPluginScript;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugins.hydrovalve.conf.HydrovalveConfig;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.bootstrap.realize.PluginCloseListener;
import com.gitee.starblues.core.PluginCloseType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @Authortfd
* @Date2024/1/8 14:57
*/
@Slf4j
@Service
public class ModbusPlugin implements PluginCloseListener {
@Autowired
private PluginInfo pluginInfo;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IPluginScript pluginScript;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IPluginConfig pluginConfig;
@Autowired
private HydrovalveConfig modbusConfig;
@Autowired
private ModbusVerticle modbusVerticle;
private Vertx vertx;
private String deployedId;
@PostConstruct
public void init(){
vertx = Vertx.vertx();
try {
//获取插件最新配置替换当前配置
Map<String, Object> config = pluginConfig.getConfig(pluginInfo.getPluginId());
BeanUtil.copyProperties(config, modbusConfig, CopyOptions.create().ignoreNullValue());
modbusVerticle.setModbusConfig(modbusConfig);
Future<String> future = vertx.deployVerticle(modbusVerticle);
future.onSuccess((s -> {
deployedId = s;
log.info("modbus plugin started success,config:"+ JsonUtils.toJsonString(modbusConfig));
}));
future.onFailure(Throwable::printStackTrace);
} catch (Throwable e) {
log.error("modbus plugin error", e);
}
}
@SneakyThrows
@Override
public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) {
log.info("plugin close,type:{},pluginId:{}", closeType, pluginInfo.getPluginId());
if (deployedId != null) {
CountDownLatch wait = new CountDownLatch(1);
Future<Void> future = vertx.undeploy(deployedId);
future.onSuccess(unused -> {
log.info("modbus plugin stopped success");
wait.countDown();
});
future.onFailure(h -> {
log.info("modbus plugin stopped failed");
h.printStackTrace();
wait.countDown();
});
wait.await(5, TimeUnit.SECONDS);
}
}
}

@ -0,0 +1,182 @@
package cc.iotkit.plugins.hydrovalve.service;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.DeviceState;
import cc.iotkit.plugin.core.thing.actions.up.DeviceRegister;
import cc.iotkit.plugin.core.thing.actions.up.DeviceStateChange;
import cc.iotkit.plugin.core.thing.actions.up.PropertyReport;
import cc.iotkit.plugins.hydrovalve.analysis.ModBusConstants;
import cc.iotkit.plugins.hydrovalve.analysis.ModBusEntity;
import cc.iotkit.plugins.hydrovalve.analysis.ModBusRtuAnalysis;
import cc.iotkit.plugins.hydrovalve.conf.HydrovalveConfig;
import cc.iotkit.plugins.hydrovalve.utils.ByteUtils;
import cn.hutool.core.util.IdUtil;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetSocket;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Authortfd
* @Date2024/1/9 17:06
*/
@Slf4j
@Service
public class ModbusVerticle extends AbstractVerticle {
@Getter
@Setter
private HydrovalveConfig modbusConfig;
private NetClient netClient;
private NetSocket socket;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
ModBusRtuAnalysis analysis=new ModBusRtuAnalysis();
@Autowired
private PluginInfo pluginInfo;
private int connectState = 0;
private long timerID;
@Override
public void start() {
log.info("init start");
}
@Scheduled(initialDelay = 2, fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void initClient() {
if (connectState > 0) {
return;
}
connectState = 1;
NetClientOptions options = new NetClientOptions();
options.setReconnectAttempts(Integer.MAX_VALUE);
options.setReconnectInterval(20000L);
netClient = vertx.createNetClient(options);
netClient.connect(modbusConfig.getPort(), modbusConfig.getHost())
.onComplete(result -> {
if (result.succeeded()) {
connectState = 2;
log.info("connect modbus slave success");
socket = result.result();
thingService.post(pluginInfo.getPluginId(), DeviceRegister.builder()
.id(UUID.randomUUID().toString())
.productKey("PYWH4r8xBzsfn3XB")
.deviceName(String.format("modbus_%d", 1))
.build());
stateChange(DeviceState.ONLINE,String.format("modbus_%d", 1));
socket.handler(data -> {
String hexStr = ByteUtils.BinaryToHexString(data.getBytes(), false);
log.info("modbus received message:{}", hexStr);
//获取功能码
if(0x03==data.getBytes()[1]){
ModBusEntity ret = analysis.unPackCmd2Entity(data.getBytes());
Map<String, Object> params = new HashMap<>();
params.put("devSwith" , Integer.parseInt(ByteUtils.BinaryToHexString(getData(ret.getData()),false)));//数据标识
thingService.post(pluginInfo.getPluginId(),
PropertyReport.builder().deviceName(String.format("modbus_%d", ret.getDevAddr())).productKey("PwMfpXmp4ZWkGahn")
.params(params)
.build()
);
}
}).closeHandler(res -> {
connectState = 0;
vertx.cancelTimer(timerID);
log.info("modbus tcp connection closed!");
stateChange(DeviceState.OFFLINE,String.format("modbus_%d", 1));
}
).exceptionHandler(res -> {
connectState = 0;
vertx.cancelTimer(timerID);
log.info("modbus tcp connection exce!");
stateChange(DeviceState.OFFLINE,String.format("modbus_%d", 1));
});
timerID = vertx.setPeriodic(modbusConfig.getInterval(), t -> {
readDataTask();
});
} else {
connectState = 0;
log.info("connect modbus tcp error", result.cause());
}
})
.onFailure(e -> {
log.error("modbus connect failed", e);
connectState = 0;
})
;
}
private void readDataTask() {
log.info("readData:" + socket);
if (socket != null) {
ModBusEntity read=new ModBusEntity();
read.setFunc(ModBusConstants.FUN_CODE3);
read.setDevAddr((byte) 1);
Integer addr=1;
Integer length=1;
String a1= StringUtils.leftPad(addr.toHexString(addr),4,'0')+StringUtils.leftPad(addr.toHexString(length),4,'0');
read.setData(ByteUtils.hexStrToBinaryStr(a1));
byte[] msg = analysis.packCmd4Entity(read);
sendMsg(msg);
}
}
@Override
public void stop() throws Exception {
if (netClient != null) {
netClient.close();
}
vertx.cancelTimer(timerID);
connectState = 0;
super.stop();
}
private byte[] getData(byte[] data) {
int lenth=data[0];
byte[] val = new byte[lenth];
System.arraycopy(data, 1, val, 0, lenth);
return val;
}
private void stateChange(DeviceState state,String deviceName) {
thingService.post(pluginInfo.getPluginId(),
DeviceStateChange.builder()
.id(IdUtil.simpleUUID())
.state(state).productKey("PYWH4r8xBzsfn3XB").deviceName(deviceName)
.time(System.currentTimeMillis())
.build());
}
public void sendMsg(byte[] msg) {
log.info("modbus send msg data:{}", ByteUtils.BinaryToHexString(msg,false));
Buffer data = Buffer.buffer(msg);
socket.write(data, r -> {
if (r.succeeded()) {
log.info("modbus msg send success:{}", ByteUtils.BinaryToHexString(msg,false));
} else {
log.error("modbus msg send failed", r.cause());
}
});
}
}

@ -0,0 +1,58 @@
package cc.iotkit.plugins.hydrovalve.utils;
/**
* @Authortfd
* @Date2024/1/8 15:15
*/
public class ByteUtils {
/**
*
*
* @param hexString
* @return
*/
public static byte[] hexStrToBinaryStr(String hexString) {
if (hexString==null) {
return null;
}
try {
hexString = hexString.replaceAll(" ", "");
int len = hexString.length();
int index = 0;
byte[] bytes = new byte[len / 2];
while (index < len) {
String sub = hexString.substring(index, index + 2);
bytes[index/2] = (byte)Integer.parseInt(sub,16);
index += 2;
}
return bytes;
}catch (Exception e){
return null;
}
}
/**
*
*
* @return
*/
public static String BinaryToHexString(byte[] bytes,boolean isBalank) {
String hexStr = "0123456789ABCDEF";
String result = "";
String hex = "";
Boolean feStart=true;
for (byte b : bytes) {
hex = String.valueOf(hexStr.charAt((b & 0xF0) >> 4));
hex += String.valueOf(hexStr.charAt(b & 0x0F));
if("FE".equals(hex) && feStart){
continue;
}else {
feStart=false;
}
result += hex + (isBalank?" ":"");
}
return result;
}
}

@ -0,0 +1,8 @@
plugin:
runMode: prod
mainPackage: cc.iotkit.plugin
hydrovalve:
host: xxxx
port: 38807
interval: 20000

@ -0,0 +1,23 @@
[
{
"id": "host",
"name": "服务端ip",
"type": "text",
"value": "25on621889.goho.co",
"desc": "服务端ip"
},
{
"id": "port",
"name": "服务端端口",
"type": "number",
"value": 38807,
"desc": "服务端端口"
},
{
"id": "interval",
"name": "采集频率",
"type": "number",
"value": 20000,
"desc": "采集频率"
}
]

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>iot-iita-plugins</artifactId>
<groupId>cc.iotkit.plugins</groupId>
<version>2.0.19</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>modbus-plugin</artifactId>
<dependencies>
<dependency>
<groupId>com.digitalpetri.modbus</groupId>
<artifactId>modbus-master-tcp</artifactId>
<version>1.2.0</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<plugin.build.mode>dev</plugin.build.mode>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<plugin.build.mode>prod</plugin.build.mode>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>${spring-brick.version}</version>
<configuration>
<mode>${plugin.build.mode}</mode>
<pluginInfo>
<id>modbus-plugin</id>
<bootstrapClass>cc.iotkit.plugins.modbus.Application</bootstrapClass>
<version>${project.version}</version>
<provider>iita</provider>
<description>modbus示例插件</description>
<configFileName>application.yml</configFileName>
</pluginInfo>
<prodConfig>
<packageType>jar</packageType>
</prodConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,21 @@
package cc.iotkit.plugins.modbus;
import com.gitee.starblues.bootstrap.SpringPluginBootstrap;
import com.gitee.starblues.bootstrap.annotation.OneselfConfig;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @author sjg
*/
@SpringBootApplication(scanBasePackages = "cc.iotkit.plugins.modbus")
@OneselfConfig(mainConfigFileName = {"application.yml"})
@EnableConfigurationProperties
@EnableScheduling
public class Application extends SpringPluginBootstrap {
public static void main(String[] args) {
new Application().run(Application.class, args);
}
}

@ -0,0 +1,36 @@
package cc.iotkit.plugins.modbus.conf;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.IPluginScript;
import cc.iotkit.plugin.core.LocalPluginConfig;
import cc.iotkit.plugin.core.LocalPluginScript;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugins.modbus.service.FakeThingService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @author sjg
*/
@Component
public class BeanConfig {
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IThingService getThingService() {
return new FakeThingService();
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginScript getPluginScript() {
return new LocalPluginScript("script.js");
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginConfig getPluginConfig(){
return new LocalPluginConfig();
}
}

@ -0,0 +1,47 @@
package cc.iotkit.plugins.modbus.service;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
/**
*
*
* @author sjg
*/
@Slf4j
public class FakeThingService implements IThingService {
@Override
public ActionResult post(String pluginId, IDeviceAction action) {
log.info("post action:{}", action);
return ActionResult.builder().code(0).build();
}
@Override
public ThingProduct getProduct(String pk) {
return ThingProduct.builder()
.productKey("cGCrkK7Ex4FESAwe")
.productSecret("aaaaaaaa")
.build();
}
@Override
public ThingDevice getDevice(String dn) {
return ThingDevice.builder()
.productKey("cGCrkK7Ex4FESAwe")
.deviceName(dn)
.build();
}
@Override
public Map<String, ?> getProperty(String dn) {
return new HashMap<>(0);
}
}

@ -0,0 +1,132 @@
package cc.iotkit.plugins.modbus.service;
import cc.iotkit.plugin.core.IPluginScript;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.DeviceState;
import cc.iotkit.plugin.core.thing.actions.up.DeviceRegister;
import cc.iotkit.plugin.core.thing.actions.up.DeviceStateChange;
import cc.iotkit.plugin.core.thing.actions.up.PropertyReport;
import cc.iotkit.script.IScriptEngine;
import com.digitalpetri.modbus.master.ModbusTcpMaster;
import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;
import com.digitalpetri.modbus.requests.ReadHoldingRegistersRequest;
import com.digitalpetri.modbus.responses.ReadHoldingRegistersResponse;
import com.fasterxml.jackson.core.type.TypeReference;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.bootstrap.realize.PluginCloseListener;
import com.gitee.starblues.core.PluginCloseType;
import com.gitee.starblues.core.PluginInfo;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.CompletableFuture;
/**
* @author sjg
*/
@Slf4j
@Service
public class ModbusPlugin implements PluginCloseListener {
@Autowired
private PluginInfo pluginInfo;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IPluginScript pluginScript;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
private IScriptEngine scriptEngine;
private final ModbusTcpMasterConfig config = new ModbusTcpMasterConfig.Builder("localhost").setPort(502).build();
private ModbusTcpMaster master;
private final Set<Integer> registeredDevice = new HashSet<>();
private final int[] slaves = new int[]{1, 2, 3};
private final Map<Integer, String> DATA_CACHE = new HashMap<>();
@PostConstruct
public void init() {
master = new ModbusTcpMaster(config);
CompletableFuture<ModbusTcpMaster> connect = master.connect();
connect.thenAccept(modbusTcpMaster -> System.out.println("111:" + modbusTcpMaster.getConfig()));
//获取脚本引擎
scriptEngine = pluginScript.getScriptEngine(pluginInfo.getPluginId());
}
@Scheduled(initialDelay = 3000, fixedDelay = 1000)
public void taskRead() {
for (int slave : slaves) {
CompletableFuture<ReadHoldingRegistersResponse> future =
master.sendRequest(new ReadHoldingRegistersRequest(0, 10), slave);
future.thenAccept(response -> {
String rspBytes = ByteBufUtil.hexDump(response.getRegisters());
ReferenceCountUtil.release(response);
log.info("receive Response: " + rspBytes);
//相同数据不处理
if (DATA_CACHE.getOrDefault(slave, "").equals(rspBytes)) {
return;
}
DATA_CACHE.put(slave, rspBytes);
if (!registeredDevice.contains(slave)) {
//第一次读取自动注册设备
thingService.post(pluginInfo.getPluginId(), DeviceRegister.builder()
.id(UUID.randomUUID().toString())
.productKey("cGCrkK7Ex4FESAwe")
.deviceName(String.format("modbus_%d", slave))
.build());
registeredDevice.add(slave);
//并上线
thingService.post(pluginInfo.getPluginId(), DeviceStateChange.builder()
.id(UUID.randomUUID().toString())
.productKey("cGCrkK7Ex4FESAwe")
.deviceName(String.format("modbus_%d", slave))
.state(DeviceState.ONLINE)
.build());
}
//调用脚本解码
Map<String, Object> rst = scriptEngine.invokeMethod(new TypeReference<>() {
}, "decode", rspBytes);
if (rst == null || rst.isEmpty()) {
return;
}
//属性上报
thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
.id(UUID.randomUUID().toString())
.productKey("cGCrkK7Ex4FESAwe")
.deviceName(String.format("modbus_%d", slave))
.params(rst)
.build());
});
}
}
@Override
public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) {
try {
log.info("plugin close,type:{},pluginId:{}", closeType, pluginInfo.getPluginId());
master.disconnect();
} catch (Throwable e) {
log.error("modbus plugin stop error", e);
}
}
}

@ -0,0 +1,5 @@
plugin:
runMode: prod
mainPackage: cc.iotkit.plugin

@ -0,0 +1,24 @@
function hexToByte(hexString) {
if (hexString.length % 2 !== 0) {
throw new Error('Invalid hex string. String must have an even number of characters.');
}
let byteArray = [];
for (let i = 0; i < hexString.length; i += 4) {
byteArray.push(parseInt(hexString.substr(i, 4), 16));
}
return byteArray;
}
this.decode=function(hex){
try{
const bytes=hexToByte(hex);
return {
"rssi":bytes[0],
"powerstate":bytes[1]
};
}catch(e){
return {};
}
}

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>iot-iita-plugins</artifactId>
<groupId>cc.iotkit.plugins</groupId>
<version>2.10.19</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mqtt-plugin</artifactId>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mqtt</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-mqtt</artifactId>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<plugin.build.mode>dev</plugin.build.mode>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<plugin.build.mode>prod</plugin.build.mode>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>${spring-brick.version}</version>
<configuration>
<mode>${plugin.build.mode}</mode>
<pluginInfo>
<id>mqtt-plugin</id>
<bootstrapClass>cc.iotkit.plugins.mqtt.Application</bootstrapClass>
<version>${project.version}</version>
<provider>iita</provider>
<description>mqtt示例插件</description>
<configFileName>application.yml</configFileName>
</pluginInfo>
<prodConfig>
<packageType>jar</packageType>
</prodConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,19 @@
package cc.iotkit.plugins.mqtt;
import com.gitee.starblues.bootstrap.SpringPluginBootstrap;
import com.gitee.starblues.bootstrap.annotation.OneselfConfig;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author sjg
*/
@SpringBootApplication(scanBasePackages = "cc.iotkit.plugins.mqtt")
@OneselfConfig(mainConfigFileName = {"application.yml"})
@EnableConfigurationProperties
public class Application extends SpringPluginBootstrap {
public static void main(String[] args) {
new Application().run(Application.class, args);
}
}

@ -0,0 +1,28 @@
package cc.iotkit.plugins.mqtt.conf;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.LocalPluginConfig;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugins.mqtt.service.FakeThingService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @author sjg
*/
@Component
public class BeanConfig {
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IThingService getThingService() {
return new FakeThingService();
}
@Bean
@ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev")
IPluginConfig getPluginConfig(){
return new LocalPluginConfig();
}
}

@ -0,0 +1,31 @@
/*
* +----------------------------------------------------------------------
* | Copyright (c) 2021-2022 All rights reserved.
* +----------------------------------------------------------------------
* | Licensed
* +----------------------------------------------------------------------
* | Author: xw2sy@163.com
* +----------------------------------------------------------------------
*/
package cc.iotkit.plugins.mqtt.conf;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "mqtt")
public class MqttConfig {
private int port;
private String sslKey;
private String sslCert;
private boolean ssl;
private boolean useWebSocket;
}

@ -0,0 +1,70 @@
package cc.iotkit.plugins.mqtt.service;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
*
*
* @author sjg
*/
@Slf4j
public class FakeThingService implements IThingService {
/**
*
*/
private static final Map<String, String> PRODUCTS = Map.of(
"hbtgIA0SuVw9lxjB", "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU",
"Rf4QSjbm65X45753", "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU",
"cGCrkK7Ex4FESAwe", "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU"
);
/**
*
*/
private static final Map<String, String> DEVICES = new HashMap<>();
static {
for (int i = 0; i < 10; i++) {
DEVICES.put("TEST:GW:" + StringUtils.leftPad(i + "", 6, "0"), "hbtgIA0SuVw9lxjB");
DEVICES.put("TEST_SW_" + StringUtils.leftPad(i + "", 6, "0"), "Rf4QSjbm65X45753");
DEVICES.put("TEST_SC_" + StringUtils.leftPad(i + "", 6, "0"), "cGCrkK7Ex4FESAwe");
}
}
@Override
public ActionResult post(String pluginId, IDeviceAction action) {
log.info("post action:{}", action);
return ActionResult.builder().code(0).build();
}
@Override
public ThingProduct getProduct(String pk) {
return ThingProduct.builder()
.productKey(pk)
.productSecret(PRODUCTS.get(pk))
.build();
}
@Override
public ThingDevice getDevice(String dn) {
return ThingDevice.builder()
.productKey(DEVICES.get(dn))
.deviceName(dn)
.build();
}
@Override
public Map<String, ?> getProperty(String dn) {
return new HashMap<>(0);
}
}

@ -0,0 +1,88 @@
package cc.iotkit.plugins.mqtt.service;
import cc.iotkit.common.enums.ErrCode;
import cc.iotkit.common.exception.BizException;
import cc.iotkit.plugin.core.thing.IDevice;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.down.DeviceConfig;
import cc.iotkit.plugin.core.thing.actions.down.PropertyGet;
import cc.iotkit.plugin.core.thing.actions.down.PropertySet;
import cc.iotkit.plugin.core.thing.actions.down.ServiceInvoke;
import io.vertx.core.json.JsonObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* mqtt
*
* @author sjg
*/
@Service
public class MqttDevice implements IDevice {
@Autowired
private MqttVerticle mqttVerticle;
@Override
public ActionResult config(DeviceConfig action) {
return ActionResult.builder().code(0).reason("").build();
}
@Override
public ActionResult propertyGet(PropertyGet action) {
String topic = String.format("/sys/%s/%s/c/service/property/get", action.getProductKey(), action.getDeviceName());
return send(
topic,
action.getDeviceName(),
new JsonObject()
.put("id", action.getId())
.put("method", "thing.service.property.get")
.put("params", action.getKeys())
.toString()
);
}
@Override
public ActionResult propertySet(PropertySet action) {
String topic = String.format("/sys/%s/%s/c/service/property/set", action.getProductKey(), action.getDeviceName());
return send(
topic,
action.getDeviceName(),
new JsonObject()
.put("id", action.getId())
.put("method", "thing.service.property.set")
.put("params", action.getParams())
.toString()
);
}
@Override
public ActionResult serviceInvoke(ServiceInvoke action) {
String topic = String.format("/sys/%s/%s/c/service/%s", action.getProductKey(), action.getDeviceName(), action.getName());
return send(
topic,
action.getDeviceName(),
new JsonObject()
.put("id", action.getId())
.put("method", "thing.service." + action.getName())
.put("params", action.getParams())
.toString()
);
}
private ActionResult send(String topic, String deviceName, String payload) {
try {
mqttVerticle.publish(
deviceName,
topic,
payload
);
return ActionResult.builder().code(0).reason("").build();
} catch (BizException e) {
return ActionResult.builder().code(e.getCode()).reason(e.getMessage()).build();
} catch (Exception e) {
return ActionResult.builder().code(ErrCode.UNKNOWN_EXCEPTION.getKey()).reason(e.getMessage()).build();
}
}
}

@ -0,0 +1,94 @@
package cc.iotkit.plugins.mqtt.service;
import cc.iotkit.plugin.core.IPlugin;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugins.mqtt.conf.MqttConfig;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.bootstrap.realize.PluginCloseListener;
import com.gitee.starblues.core.PluginCloseType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @author sjg
*/
@Slf4j
@Service
public class MqttPlugin implements PluginCloseListener, IPlugin {
@Autowired
private PluginInfo pluginInfo;
@Autowired
private MqttVerticle mqttVerticle;
@Autowired
private MqttConfig mqttConfig;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IPluginConfig pluginConfig;
private Vertx vertx;
private String deployedId;
@PostConstruct
public void init() {
vertx = Vertx.vertx();
try {
//获取插件最新配置替换当前配置
Map<String, Object> config = pluginConfig.getConfig(pluginInfo.getPluginId());
BeanUtil.copyProperties(config, mqttConfig, CopyOptions.create().ignoreNullValue());
mqttVerticle.setConfig(mqttConfig);
Future<String> future = vertx.deployVerticle(mqttVerticle);
future.onSuccess((s -> {
deployedId = s;
log.info("mqtt plugin started success");
}));
future.onFailure((e) -> {
log.error("mqtt plugin startup failed", e);
});
} catch (Throwable e) {
log.error("mqtt plugin startup error", e);
}
}
@Override
public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) {
try {
log.info("plugin close,type:{},pluginId:{}", closeType, pluginInfo.getPluginId());
if (deployedId != null) {
CountDownLatch wait = new CountDownLatch(1);
Future<Void> future = vertx.undeploy(deployedId);
future.onSuccess(unused -> {
log.info("mqtt plugin stopped success");
wait.countDown();
});
future.onFailure(h -> {
log.info("tcp plugin stopped failed");
h.printStackTrace();
wait.countDown();
});
wait.await(5, TimeUnit.SECONDS);
}
} catch (Throwable e) {
log.error("mqtt plugin stop error", e);
}
}
@Override
public Map<String, Object> getLinkInfo(String pk, String dn) {
return null;
}
}

@ -0,0 +1,443 @@
/*
* +----------------------------------------------------------------------
* | Copyright (c) 2021-2022 All rights reserved.
* +----------------------------------------------------------------------
* | Licensed
* +----------------------------------------------------------------------
* | Author: xw2sy@163.com
* +----------------------------------------------------------------------
*/
package cc.iotkit.plugins.mqtt.service;
import cc.iotkit.common.enums.ErrCode;
import cc.iotkit.common.exception.BizException;
import cc.iotkit.common.utils.CodecUtil;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.common.utils.UniqueIdUtil;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.ActionResult;
import cc.iotkit.plugin.core.thing.actions.DeviceState;
import cc.iotkit.plugin.core.thing.actions.EventLevel;
import cc.iotkit.plugin.core.thing.actions.IDeviceAction;
import cc.iotkit.plugin.core.thing.actions.up.*;
import cc.iotkit.plugin.core.thing.model.ThingDevice;
import cc.iotkit.plugin.core.thing.model.ThingProduct;
import cc.iotkit.plugins.mqtt.conf.MqttConfig;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.core.PluginInfo;
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
import io.netty.handler.codec.mqtt.MqttProperties;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.PemKeyCertOptions;
import io.vertx.mqtt.*;
import io.vertx.mqtt.messages.codes.MqttSubAckReasonCode;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* mqtt
* http://iotkit-open-source.gitee.io/document/pages/device_protocol/mqtt/#%E7%BD%91%E5%85%B3%E8%BF%9E%E6%8E%A5%E5%92%8C%E6%B3%A8%E5%86%8C
*
* @author sjg
*/
@Slf4j
@Component
@Data
public class MqttVerticle extends AbstractVerticle implements Handler<MqttEndpoint> {
private MqttServer mqttServer;
private final Map<String, MqttEndpoint> endpointMap = new HashMap<>();
/**
* clientid-mqttmqtthandler线
*/
private static final Map<String, Boolean> MQTT_CONNECT_POOL = new ConcurrentHashMap<>();
private static final Map<String, Boolean> DEVICE_ONLINE = new ConcurrentHashMap<>();
private MqttConfig config;
@Autowired
@AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
private IThingService thingService;
@Autowired
private PluginInfo pluginInfo;
@Override
public void start() {
Executors.newSingleThreadScheduledExecutor().schedule(this::initMqttServer, 3, TimeUnit.SECONDS);
}
private void initMqttServer() {
MqttServerOptions options = new MqttServerOptions()
.setPort(config.getPort());
if (config.isSsl()) {
options = options.setSsl(true)
.setKeyCertOptions(new PemKeyCertOptions()
.setKeyPath(config.getSslKey())
.setCertPath(config.getSslCert()));
}
options.setUseWebSocket(config.isUseWebSocket());
options.setTcpKeepAlive(true);
mqttServer = MqttServer.create(vertx, options);
mqttServer.endpointHandler(this).listen(ar -> {
if (ar.succeeded()) {
log.info("MQTT server is listening on port " + ar.result().actualPort());
} else {
log.error("Error on starting the server", ar.cause());
}
});
}
@Override
public void handle(MqttEndpoint endpoint) {
log.info("MQTT client:{} request to connect, clean session = {}", endpoint.clientIdentifier(), endpoint.isCleanSession());
MqttAuth auth = endpoint.auth();
if (auth == null) {
endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED);
return;
}
//mqtt连接认证信息
/*
* mqttClientId: productKey_deviceName_model
* mqttUserName: deviceName
* mqttPassword: md5(,mqttClientId)
*/
String clientId = endpoint.clientIdentifier();
String[] parts = clientId.split("_");
if (parts.length < 3) {
log.error("clientId:{}不正确", clientId);
endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_CLIENT_IDENTIFIER_NOT_VALID);
return;
}
log.info("MQTT client auth,clientId:{},username:{},password:{}",
clientId, auth.getUsername(), auth.getPassword());
String productKey = parts[0];
String deviceName = parts[1];
String gwModel = parts[2];
if (!auth.getUsername().equals(deviceName)) {
log.error("username:{}不正确", deviceName);
endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD);
return;
}
ThingProduct product = thingService.getProduct(productKey);
if (product == null) {
log.error("获取产品信息失败,productKey:{}", productKey);
endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD);
return;
}
String validPasswd = CodecUtil.md5Str(product.getProductSecret() + clientId);
if (!validPasswd.equalsIgnoreCase(auth.getPassword())) {
log.error("密码验证失败,期望值:{}", validPasswd);
endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD);
return;
}
//网关设备注册
ActionResult result = thingService.post(
pluginInfo.getPluginId(),
fillAction(
DeviceRegister.builder()
.productKey(productKey)
.deviceName(deviceName)
.model(gwModel)
.version("1.0")
.build()
)
);
if (result.getCode() != 0) {
log.error("设备注册失败:{}", result);
endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED);
return;
}
//保存设备与连接关系
endpointMap.put(deviceName, endpoint);
MQTT_CONNECT_POOL.put(clientId, true);
log.info("MQTT client keep alive timeout = {} ", endpoint.keepAliveTimeSeconds());
endpoint.accept(false);
endpoint.closeHandler((v) -> {
// 网络不好时也会出发,但是设备仍然可以发消息
log.warn("client connection closed,clientId:{}", clientId);
if (Boolean.FALSE.equals(MQTT_CONNECT_POOL.get(clientId))) {
MQTT_CONNECT_POOL.remove(clientId);
return;
}
//下线
offline(productKey, deviceName);
DEVICE_ONLINE.clear();
//删除设备与连接关系
endpointMap.remove(deviceName);
}).disconnectMessageHandler(disconnectMessage -> {
log.info("Received disconnect from client, reason code = {}", disconnectMessage.code());
if (!MQTT_CONNECT_POOL.get(clientId)) {
return;
}
//下线
offline(productKey, deviceName);
//删除设备与连接关系
endpointMap.remove(deviceName);
MQTT_CONNECT_POOL.put(clientId, false);
DEVICE_ONLINE.clear();
}).subscribeHandler(subscribe -> {
List<MqttSubAckReasonCode> reasonCodes = new ArrayList<>();
for (MqttTopicSubscription s : subscribe.topicSubscriptions()) {
log.info("Subscription for {},with QoS {}", s.topicName(), s.qualityOfService());
try {
String topic = s.topicName();
ThingDevice device = getDevice(topic);
//添加设备对应连接
endpointMap.put(device.getDeviceName(), endpoint);
online(device.getProductKey(), device.getDeviceName());
reasonCodes.add(MqttSubAckReasonCode.qosGranted(s.qualityOfService()));
} catch (Throwable e) {
log.error("subscribe failed,topic:" + s.topicName(), e);
reasonCodes.add(MqttSubAckReasonCode.NOT_AUTHORIZED);
}
}
// ack the subscriptions request
endpoint.subscribeAcknowledge(subscribe.messageId(), reasonCodes, MqttProperties.NO_PROPERTIES);
}).unsubscribeHandler(unsubscribe -> {
for (String topic : unsubscribe.topics()) {
ThingDevice device = getDevice(topic);
//删除设备对应连接
endpointMap.remove(device.getDeviceName());
//下线
offline(device.getProductKey(), device.getDeviceName());
DEVICE_ONLINE.remove(device.getDeviceName());
}
// ack the subscriptions request
endpoint.unsubscribeAcknowledge(unsubscribe.messageId());
}).publishHandler(message -> {
String topic = message.topicName();
JsonObject payload = message.payload().toJsonObject();
log.info("Received message:topic={},payload={}, with QoS {}", topic, payload,
message.qosLevel());
if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) {
endpoint.publishAcknowledge(message.messageId());
} else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) {
endpoint.publishReceived(message.messageId());
}
if (payload.isEmpty()) {
return;
}
ThingDevice device = getDevice(topic);
if (device == null) {
return;
}
//有消息上报-设备上线
online(device.getProductKey(), device.getDeviceName());
if (!MQTT_CONNECT_POOL.get(clientId)) {
//保存设备与连接关系
endpointMap.put(deviceName, endpoint);
MQTT_CONNECT_POOL.put(clientId, true);
log.info("mqtt client reconnect success,clientId:{}", clientId);
}
try {
JsonObject defParams = JsonObject.mapFrom(new HashMap<>(0));
IDeviceAction action = null;
String method = payload.getString("method", "");
if (StringUtils.isBlank(method)) {
return;
}
JsonObject params = payload.getJsonObject("params", defParams);
if ("thing.lifetime.register".equalsIgnoreCase(method)) {
//子设备注册
String subPk = params.getString("productKey");
String subDn = params.getString("deviceName");
ActionResult regResult = thingService.post(
pluginInfo.getPluginId(),
fillAction(
SubDeviceRegister.builder()
.productKey(productKey)
.deviceName(deviceName)
.model(gwModel)
.version("1.0")
.subs(List.of(
DeviceRegister.builder()
.productKey(subPk)
.deviceName(subDn)
.model(params.getString("model"))
.version("1.0")
.build()
))
.build()
)
);
if (regResult.getCode() == 0) {
//注册成功
reply(endpoint, topic, payload);
} else {
//注册失败
reply(endpoint, topic, new JsonObject(), regResult.getCode());
}
return;
}
if ("thing.event.property.post".equalsIgnoreCase(method)) {
//属性上报
action = PropertyReport.builder()
.params(params.getMap())
.build();
reply(endpoint, topic, payload);
} else if (method.startsWith("thing.event.")) {
//事件上报
action = EventReport.builder()
.name(method.replace("thing.event.", ""))
.level(EventLevel.INFO)
.params(params.getMap())
.build();
reply(endpoint, topic, payload);
} else if (method.startsWith("thing.service.") && method.endsWith("_reply")) {
//服务回复
action = ServiceReply.builder()
.name(method.replaceAll("thing\\.service\\.(.*)_reply", "$1"))
.code(payload.getInteger("code", 0))
.params(params.getMap())
.build();
}
if (action == null) {
return;
}
action.setId(payload.getString("id"));
action.setProductKey(device.getProductKey());
action.setDeviceName(device.getDeviceName());
action.setTime(System.currentTimeMillis());
thingService.post(pluginInfo.getPluginId(), action);
} catch (Throwable e) {
log.error("handler message failed,topic:" + message.topicName(), e);
}
}).publishReleaseHandler(endpoint::publishComplete);
}
public void online(String pk, String dn) {
if (Boolean.TRUE.equals(DEVICE_ONLINE.get(dn))) {
return;
}
//上线
thingService.post(
pluginInfo.getPluginId(),
fillAction(DeviceStateChange.builder()
.productKey(pk)
.deviceName(dn)
.state(DeviceState.ONLINE)
.build()
)
);
DEVICE_ONLINE.put(dn, true);
}
/**
*
*/
private void reply(MqttEndpoint endpoint, String topic, JsonObject payload) {
reply(endpoint, topic, payload, 0);
}
/**
*
*/
private void reply(MqttEndpoint endpoint, String topic, JsonObject payload, int code) {
Map<String, Object> payloadReply = new HashMap<>();
payloadReply.put("id", payload.getString("id"));
payloadReply.put("method", payload.getString("method") + "_reply");
payloadReply.put("code", code);
payloadReply.put("data", payload.getJsonObject("params"));
endpoint.publish(topic + "_reply", JsonObject.mapFrom(payloadReply).toBuffer(), MqttQoS.AT_LEAST_ONCE, false, false);
}
private IDeviceAction fillAction(IDeviceAction action) {
action.setId(UniqueIdUtil.newRequestId());
action.setTime(System.currentTimeMillis());
return action;
}
@Override
public void stop() {
for (MqttEndpoint endpoint : endpointMap.values()) {
String clientId = endpoint.clientIdentifier();
String[] parts = clientId.split("_");
if (parts.length < 3) {
continue;
}
//下线
offline(parts[0], parts[1]);
DEVICE_ONLINE.clear();
}
if(mqttServer!=null) {
mqttServer.close();
}
}
private void offline(String productKey, String deviceName) {
thingService.post(
pluginInfo.getPluginId(),
fillAction(
DeviceStateChange.builder()
.productKey(productKey)
.deviceName(deviceName)
.state(DeviceState.OFFLINE)
.build()
)
);
}
public void publish(String deviceName, String topic, String msg) {
MqttEndpoint endpoint = endpointMap.get(deviceName);
if (endpoint == null) {
throw new BizException(ErrCode.SEND_DESTINATION_NOT_FOUND);
}
Future<Integer> result = endpoint.publish(topic, Buffer.buffer(msg),
MqttQoS.AT_LEAST_ONCE, false, false);
result.onFailure(e -> log.error("public topic failed", e));
result.onSuccess(integer -> log.info("publish success,topic:{},payload:{}", topic, msg));
}
public ThingDevice getDevice(String topic) {
String[] topicParts = topic.split("/");
if (topicParts.length < 5) {
return null;
}
return ThingDevice.builder()
.productKey(topicParts[2])
.deviceName(topicParts[3])
.build();
}
}

@ -0,0 +1,14 @@
package cc.iotkit.plugins.mqtt.service;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.mqtt.MqttDecoder;
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("initChannel设置最大帧大小为");
ch.pipeline().addLast("decoder", new MqttDecoder(10485760)); // c10MB
// 其他处理器...
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save