Initial commit

master
小鱼干 4 months ago
commit 34ea26c4b6

@ -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 | 否 | 需要 | 不需要 |
| | | | |
接口测试:
性能测试:
并发测试:
其他:

33
.gitignore vendored

@ -0,0 +1,33 @@
# Compiled class file
*.class
# Log file
log
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.idea
target
*.iml
data/elasticsearch
.init
*.db
.flattened-pom.xml
.DS_Store
dependency-reduced-pom.xml

@ -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,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -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/)

@ -0,0 +1,37 @@
# iot-iita-plugins
#### 介绍
官方插件示例仓库
#### 软件架构
软件架构说明
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
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.19</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,64 @@
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 = calculateCrc(data);
return String.format("%04X", crc);
}
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;
String header = "AA" + type + version + length + mid + cmd;
String crcData = length + mid + cmd + dataHex;
String crc = calculate(crcData);
return header + dataHex + crc + "55";
}
}

@ -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,53 @@
package cc.iotkit.plugins.emqx.conf;
import java.util.HashMap;
import java.util.Map;
public class EventTypeMapper {
private static final Map<Integer, String> EVENT_TYPE_MAP = new HashMap<>();
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(21, "主电欠压恢复");
// 动火离人相关事件
EVENT_TYPE_MAP.put(22, "设备开机");
EVENT_TYPE_MAP.put(23, "动火离人报警");
EVENT_TYPE_MAP.put(24, "动火离人报警恢复");
EVENT_TYPE_MAP.put(25, "设备故障");
EVENT_TYPE_MAP.put(26, "设备故障恢复");
EVENT_TYPE_MAP.put(27, "断网故障");
EVENT_TYPE_MAP.put(28, "断网故障恢复");
// 正常上报事件
EVENT_TYPE_MAP.put(128, "燃气报警器正常数据上报");
EVENT_TYPE_MAP.put(129, "动火离人正常数据上报");
}
public static String getEventTypeName(int eventType) {
return EVENT_TYPE_MAP.getOrDefault(eventType, "未知事件类型");
}
}

@ -0,0 +1,201 @@
package cc.iotkit.plugins.emqx.conf;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.*;
public class GasAlarmDataParser {
public static void main(String[] args) {
// 节点相关事件测试数据 (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 {
List<Map<String, Object>> result = GasAlarmDataParser.parseHexDataToList(hexData);
System.out.println("原始数据: " + hexData);
System.out.println("解析结果: " + result.get(0).get("eventType")
+ " - " + result.get(0).get("dataType"));
System.out.println("rrr解析结果: " + result.get(0));
} catch (Exception e) {
System.out.println("解析异常: " + e.getMessage());
}
}
}
public static List<Map<String, Object>> parseHexDataToList(String hexData) {
byte[] data = hexStringToByteArray(hexData);
return parseDataToList(data);
}
public static List<Map<String, Object>> parseDataToList(byte[] data) {
List<Map<String, Object>> resultList = new ArrayList<>();
if (data == null || data.length < 1) {
return resultList;
}
int eventType = Byte.toUnsignedInt(data[0]) + Byte.toUnsignedInt(data[1]);
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 List<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");
eventMap.put("eventType", buffer.getShort() & 0xFFFF);
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(Integer.parseInt(eventMap.get("eventType").toString())));
eventMap.put("timestamp", parseBcdTime(buffer));
eventList.add(eventMap);
return eventList;
}
private static List<Map<String, Object>> parseNodeEventToList(byte[] data) {
List<Map<String, Object>> eventList = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.wrap(data);
Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put("dataType", "NODE_EVENT");
eventMap.put("eventType", buffer.getShort() & 0xFFFF);
eventMap.put("eventTypeValue", EventTypeMapper.getEventTypeName(Integer.parseInt(eventMap.get("eventType").toString())));
eventMap.put("timestamp", parseBcdTime(buffer));
eventMap.put("nodeId", buffer.getShort() & 0xFFFF);
eventMap.put("dataValue", buffer.getShort() & 0xFFFF);
eventList.add(eventMap);
return eventList;
}
private static List<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");
baseInfo.put("eventType", buffer.getShort() & 0xFFFF);
baseInfo.put("eventTypeValue", EventTypeMapper.getEventTypeName(Integer.parseInt(baseInfo.get("eventType").toString())));
baseInfo.put("timestamp", parseBcdTime(buffer));
baseInfo.put("totalNodes", buffer.getShort() & 0xFFFF);
baseInfo.put("reportedNodes", buffer.get() & 0xFF);
dataList.add(baseInfo);
// 节点数据
while (buffer.remaining() >= 8) {
Map<String, Object> nodeMap = new LinkedHashMap<>();
nodeMap.put("nodeId", buffer.getShort() & 0xFFFF);
nodeMap.put("nodeType", buffer.get() & 0xFF);
nodeMap.put("unit", buffer.get() & 0xFF);
nodeMap.put("precision", buffer.get() & 0xFF);
nodeMap.put("gasType", buffer.get() & 0xFF);
nodeMap.put("dataValue", buffer.getShort() & 0xFFFF);
if(nodeMap.get("nodeType").equals("0")){
// 0节点为探测器
nodeMap.put("dataTypeValue", nodeMap.get("dataValue"));
}else if (nodeMap.get("nodeType").equals("1")){
//1节点为输出模块
nodeMap.put("dataTypeValue",nodeMap.get("dataValue").equals("0")?"未动作":"已动作");
}
dataList.add(nodeMap);
}
return dataList;
}
// 保留原有辅助方法
private static String parseBcdTime(ByteBuffer buffer) {
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");
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 >= 13 && eventType <= 21;
}
}

@ -0,0 +1,197 @@
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 String generateReadCommand(int terminalType, int configId) {
ByteBuffer buf = ByteBuffer.allocate(4);
// 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 terminalType, int configId, byte[] data) {
ByteBuffer buf = ByteBuffer.allocate(5 + 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));
}
return result;
}
// 特殊格式解析方法
private static String parseTLVString(byte[] data) {
int len = data[0] & 0xFF;
return new String(data, 1, Math.min(len, data.length-1), StandardCharsets.US_ASCII);
}
private static String parseSimCard(byte[] data) {
int len = data[0] & 0xFF;
return new String(data, 1, Math.min(len, 20), StandardCharsets.US_ASCII);
}
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 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;
}
/*
*
* */
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, data, 1, strBytes.length);
return data;
}
// 构建BCD时间数据(YYMMDDhhmmss)
public static byte[] buildBcdTimeData(String time) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMddHHmmss");
String formatted = LocalDateTime.now().format(formatter);
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,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,56 @@
package cc.iotkit.plugins.emqx.conf;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import static cc.iotkit.plugins.emqx.conf.IoTConfigProtocol.buildTLVData;
public class ProtocolTest {
public static void main(String[] args) {
// 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);
// 3. SIM卡号读取 (只读)
String readCardCmd = IoTConfigProtocol.generateReadCommand(0x01,
IoTConfigProtocol.CARD);
System.out.println("读取SIM卡指令: " + readCardCmd);
// 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,507 @@
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.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.plugins.emqx.conf.DeviceStatusParser;
import cc.iotkit.plugins.emqx.conf.GasAlarmDataParser;
import cc.iotkit.plugins.emqx.conf.MqttConfig;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.IdUtil;
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.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.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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;
/**
* @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<>();
@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("server")
.setUsername("server")
.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 -> {
String topic = msg.topicName();
log.info("topic={}",topic);
if (topic.contains("/c/")) {
return;
}
if (topic != null && !topic.startsWith("/sys/") && topic.endsWith("/rtdvalue/report")){
String s = Arrays.asList(topic.split("/")).get(1);
topic = "/sys/*/"+s+"/s/event/property/post";
log.info("Client received message on [{}] payload [{}] with QoS [{}]", topic, msg.payload().toJsonObject(), msg.qosLevel());
}
JsonObject payload = msg.payload().toJsonObject();
try {
//客户端连接断开
if (topic.equals("/sys/client/disconnected")) {
offline(payload.getString("clientid"));
return;
}
ThingDevice device = getDevice(topic);
if (device == null) {
return;
}
//有消息上报-设备上线
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_reply";
}
if (StringUtils.isBlank(method)) {
return;
}
JsonObject params = null;
if(!StringUtils.isBlank(payload.getString("method", ""))){
params = payload.getJsonObject("params", defParams);
}else{
String cmd =payload.getString("cmd");
String data =payload.getString("data");
if(ObjectUtils.isNotEmpty(cmd)&& ObjectUtils.isNotEmpty(data)){
if(cmd.equals("11")){
//4.1 状态信息上报命令码0x11
Map<String, Object> map = DeviceStatusParser.parseStatusReportToMap(data);
// params= payload.getJsonObject(DeviceStatusParser.parseStatusReportToMap(data),defParams);
payload.put("params",map);
device.setDeviceName(map.get("imei").toString());
switch(map.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");
break;
}
}else if(cmd.equals("12")){
//4.2 燃气报警器物联网模块数据上报0x12
payload.put("params",GasAlarmDataParser.parseHexDataToList(data));
// GasAlarmDataParser.parseHexDataToList(data);
}else if(cmd.equals("14")){
}
}
// params =
//解析设备数据
}
params = payload.getJsonObject("params", defParams);
// 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");
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()
)
);
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();
}
try {
reply(topic, payload, 0);
}catch (Exception e){
log.error("reply error", e);
}
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 (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 < 5) {
return null;
}
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;
}
//上线
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 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",
"01","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;
client.publish(topic, JsonObject.mapFrom(payloadReply).toBuffer(), 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,186 @@
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.json.JsonObject;
import io.vertx.mqtt.MqttClient;
import lombok.Setter;
import org.springframework.stereotype.Service;
import java.nio.ByteBuffer;
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;
// int configId = getConfigId(key);
/* if (configId == -1) {
System.err.println("Unknown config key: " + key);
continue;
}
*/
switch(key.toLowerCase()) {
case "ip/realm":
configId = IP_REALM;
tlvData = buildTLVData(value.getBytes());
break;
case "port":
case "keepalive":
case "reportrateupper":
configId = (key.toLowerCase().equals("port") ? PORT : key.toLowerCase().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.toLowerCase().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.toLowerCase().equals("publishtopicprefix") ? PUBLISH_TOPIC :SUBSCRIBE_TOPIC);
tlvData = buildStringData(value.toString(), 30);
break;
case "timesyncinternal":
configId = TIME_SYNC;
tlvData = buildBcdTimeData(value);
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(
0x01, configId, tlvData);
map.put("date",command);
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;
}
@Override
public ActionResult config(DeviceConfig action) {
String topic = String.format("/sys/%s/%s/c/config/set", action.getProductKey(), action.getDeviceName());
// Map data = JsonUtils.parseObject(action.getConfig(), Map.class);
List<Map> list= processConfig(action.getConfig());
for (int i = 0; i < list.size(); i++) {
//循环发送配置指令
String replyMessage = buildFrame("01","01",
"01","01","01"+ list.get(i).get("data"));
send(
topic,
new JsonObject()
.put("id", action.getId())
.put("method", "thing.config.set")
.put("params", action.getConfig())
.put("data", replyMessage)
);
}
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();
}
}
}

@ -0,0 +1,9 @@
plugin:
runMode: dev
mainPackage: cc.iotkit.plugin
emqx:
host: 127.0.0.1
port: 1883
topics: /sys/#
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
// 其他处理器...
}
}

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

@ -0,0 +1,9 @@
[
{
"id": "port",
"name": "端口",
"type": "number",
"value": 1883,
"desc": "mqtt端口默认为1883"
}
]

@ -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">
<modelVersion>4.0.0</modelVersion>
<groupId>cc.iotkit.plugins</groupId>
<artifactId>mqtt-test-client</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mqtt</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.13</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.13</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,10 @@
package cc.iotkit.test.mqtt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = {"cc.iotkit"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@ -0,0 +1,24 @@
package cc.iotkit.test.mqtt.config;
import cc.iotkit.test.mqtt.performance.ConnectionTest;
import cc.iotkit.test.mqtt.performance.ReportTest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class BeanConfig {
@Bean
@ConditionalOnProperty(name = "case", havingValue = "ConnectionTest")
ConnectionTest getConnectionTest() {
return new ConnectionTest();
}
@Bean
@ConditionalOnProperty(name = "case", havingValue = "ReportTest")
ReportTest getReportTest() {
return new ReportTest();
}
}

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

Loading…
Cancel
Save