commit 28e36cb4d2da9fcbb67be6e828eff13ae661a327
Author: zhangjinbang <13002584422@163.com>
Date: Mon Dec 25 17:25:10 2023 +0800
111
diff --git a/iot-energy/pom.xml b/iot-energy/pom.xml
new file mode 100644
index 0000000..3fca93d
--- /dev/null
+++ b/iot-energy/pom.xml
@@ -0,0 +1,113 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.1.RELEASE
+
+
+
+ org.example
+ iot-energy
+ 1.0-SNAPSHOT
+
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+
+
+ io.netty
+ netty-all
+ 4.1.6.Final
+
+
+ net.sf.json-lib
+ json-lib
+ 2.4
+ jdk15
+
+
+
+ com.alibaba
+ fastjson
+ 1.2.70
+
+
+
+ mysql
+ mysql-connector-java
+ 5.1.49
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.4.1
+
+
+ com.baomidou
+ dynamic-datasource-spring-boot-starter
+ 3.4.1
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.1.10
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.0
+ provided
+
+
+
+ commons-codec
+ commons-codec
+ 1.4
+
+
+
+ com.ghgande
+ j2mod
+ 2.5.3
+
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.16.0
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.1.1.RELEASE
+
+
+
+ repackage
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iot-energy/src/main/java/org/example/IotApplication.java b/iot-energy/src/main/java/org/example/IotApplication.java
new file mode 100644
index 0000000..13173a9
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/IotApplication.java
@@ -0,0 +1,30 @@
+package org.example;
+
+import org.example.socket.BootNettySocketServerThread;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
+@MapperScan({"org.example.background.dao"})
+@EnableScheduling //允许自动调用
+public class IotApplication implements CommandLineRunner {
+
+ @Autowired
+ private BootNettySocketServerThread socketServerThread;
+
+ public static void main(String[] args) {
+ SpringApplication app = new SpringApplication(IotApplication.class);
+ app.run(args);
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ socketServerThread.start();
+ }
+
+}
\ No newline at end of file
diff --git a/iot-energy/src/main/java/org/example/background/condition/EnergyDataCondition.java b/iot-energy/src/main/java/org/example/background/condition/EnergyDataCondition.java
new file mode 100644
index 0000000..9186c78
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/background/condition/EnergyDataCondition.java
@@ -0,0 +1,36 @@
+package org.example.background.condition;
+
+import org.apache.ibatis.jdbc.SQL;
+import org.example.background.entity.EnergyDataEntity;
+import org.example.background.utils.FieldUtils;
+import org.springframework.cglib.beans.BeanMap;
+
+public class EnergyDataCondition {
+
+ public String insert(EnergyDataEntity data){
+ SQL sql = new SQL();
+ sql.INSERT_INTO("energy_data");
+ BeanMap beanMap = BeanMap.create(data);
+ for (Object key : beanMap.keySet()) {
+ Object val = beanMap.get(key);
+ if (val != null) {
+ sql.VALUES(FieldUtils.camelToLine(key + ""), "#{" + key + "}");
+ }
+ }
+ return sql.toString();
+ }
+
+ public String updateByPrimaryKey(EnergyDataEntity data) {
+ SQL sql = new SQL();
+ sql.UPDATE("energy_data");
+ BeanMap beanMap = BeanMap.create(data);
+ for (Object key : beanMap.keySet()) {
+ Object val = beanMap.get(key);
+ if (val != null) {
+ sql.SET(FieldUtils.camelToLine(key + "") + "=#{" + key + "}");
+ }
+ }
+ sql.WHERE("energy_id=#{energyId}");
+ return sql.toString();
+ }
+}
diff --git a/iot-energy/src/main/java/org/example/background/dao/EnergyDataDao.java b/iot-energy/src/main/java/org/example/background/dao/EnergyDataDao.java
new file mode 100644
index 0000000..ed07850
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/background/dao/EnergyDataDao.java
@@ -0,0 +1,20 @@
+package org.example.background.dao;
+
+
+import org.apache.ibatis.annotations.InsertProvider;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.UpdateProvider;
+import org.example.background.condition.EnergyDataCondition;
+import org.example.background.entity.EnergyDataEntity;
+
+public interface EnergyDataDao {
+
+ @InsertProvider(type = EnergyDataCondition.class, method = "insert")
+ Integer insert(EnergyDataEntity data);
+
+ @Select("SELECT * FROM energy_data WHERE device_Id = #{deviceId} AND acquisition_time = #{acquisitionTime}")
+ EnergyDataEntity select(EnergyDataEntity data);
+
+ @UpdateProvider(type = EnergyDataCondition.class, method = "updateByPrimaryKey")
+ Integer updateByPrimaryKey(EnergyDataEntity data);
+}
diff --git a/iot-energy/src/main/java/org/example/background/entity/EnergyDataEntity.java b/iot-energy/src/main/java/org/example/background/entity/EnergyDataEntity.java
new file mode 100644
index 0000000..18537d4
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/background/entity/EnergyDataEntity.java
@@ -0,0 +1,69 @@
+package org.example.background.entity;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+
+@Data
+public class EnergyDataEntity implements Serializable{
+
+ private Long energyId;
+
+ private String deviceId;
+
+ //@ApiModelProperty(value = "瞬时流量")
+ private Float instantaneousDelivery;
+
+ //@ApiModelProperty(value = "瞬时热量")
+ private Float instantHeat;
+
+ //@ApiModelProperty(value = "正累积流量")
+ private Float positiveTotalFlow;
+
+ //@ApiModelProperty(value = "负累积流量")
+ private Float negativeTotalFlow;
+
+ //@ApiModelProperty(value = "净累积流量")
+ private Float cumulativeTraffic;
+
+ //@ApiModelProperty(value = "供水温度")
+ private Float supplyWaterTemperature;
+
+ //@ApiModelProperty(value = "回水温度")
+ private Float returnWaterTemperature;
+
+ //@ApiModelProperty(value = "流体速度")
+ private String fluidVelocity;
+
+ //@ApiModelProperty(value = "测量流体声速")
+ private String fluidVelocitySound;
+
+ //@ApiModelProperty(value = "正累积热量")
+ private Float positiveCumulativeHeat;
+
+ //@ApiModelProperty(value = "负累积热量")
+ private Float negativeCumulativeHeat;
+
+ //@ApiModelProperty(value = "净累积热量")
+ private Float netCumulativeHeat;
+
+ //@ApiModelProperty(value = "今天累积流量")
+ private Float accumulatedTrafficDay;
+
+ //@ApiModelProperty(value = "本月累积流量")
+ private Float accumulatedTrafficMonth;
+
+ //@ApiModelProperty(value = "今年累积流量")
+ private Float accumulatedTrafficYear;
+
+ //@ApiModelProperty(value = "入库时间")
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createTime;
+
+ //@ApiModelProperty(value = "整点采集时间")
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date acquisitionTime;
+}
diff --git a/iot-energy/src/main/java/org/example/background/service/MakeInfoService.java b/iot-energy/src/main/java/org/example/background/service/MakeInfoService.java
new file mode 100644
index 0000000..38d9631
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/background/service/MakeInfoService.java
@@ -0,0 +1,198 @@
+package org.example.background.service;
+
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+import org.example.background.dao.EnergyDataDao;
+import org.example.background.entity.EnergyDataEntity;
+import org.example.utils.IdService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.nio.ByteBuffer;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * author zjb
+ * 服务端发送指令给下位
+ */
+@Component
+public class MakeInfoService {
+
+ @Resource
+ private EnergyDataDao energyDataDao;
+
+ @Resource
+ private IdService idService;
+
+ private Lock lock = new ReentrantLock();
+
+ //读取0001-0100的数据共100个寄存器 能量表
+ public int[] sendEnergyMessage () {
+ int [] data = new int[8];
+ data[0] = 0x01;
+ data[1] = 0x03;
+ data[2] = 0x00;
+ data[3] = 0x00;
+ data[4] = 0x00;
+ data[5] = 0x64;
+ String result = Integer.toHexString(getCrc(data));
+ data[6] = Integer.parseInt(result.substring(2, 4), 16);
+ data[7] = Integer.parseInt(result.substring(0, 2), 16);
+ return data;
+ }
+
+ //读取1529的数据共2个寄存器 获取设备编号 能量表
+ public int[] sendEnergyGetDevId () {
+ int [] data = new int[8];
+ data[0] = 0x01;
+ data[1] = 0x03;
+ data[2] = 0x05;
+ data[3] = 0xF9;
+ data[4] = 0x00;
+ data[5] = 0x01;
+ String result = Integer.toHexString(getCrc(data));
+ data[6] = Integer.parseInt(result.substring(2, 4), 16);
+ data[7] = Integer.parseInt(result.substring(0, 2), 16);
+ return data;
+ }
+
+ //读取0101-0150的数据共50个寄存器 获取设备编号 能量表
+ public int[] sendEnergyGetDevId50 () {
+ int [] data = new int[8];
+ data[0] = 0x01;
+ data[1] = 0x03;
+ data[2] = 0x00;
+ data[3] = 0x64;
+ data[4] = 0x00;
+ data[5] = 0x32;
+ String result = Integer.toHexString(getCrc(data));
+ data[6] = Integer.parseInt(result.substring(2, 4), 16);
+ data[7] = Integer.parseInt(result.substring(0, 2), 16);
+ return data;
+ }
+
+
+ //获取指令最后两位(CRC校验位)
+ public int getCrc (int [] bytes) {
+ int crc = 0xFFFF; //初始化寄存器
+ int polynomial = 0xA001; // 多项式A001对应的二进制数
+ for (int i = 0; i < bytes.length - 2; i++) {
+ crc ^= bytes[i];
+ for (int j = 0; j < 8; j++) {
+ if ((crc & 0x0001) == 1) {
+ crc >>= 1;
+ crc ^= polynomial;
+ } else {
+ crc >>= 1;
+ }
+ }
+ }
+ return crc;
+ }
+
+ //响应报文转明文
+ public String convertPlaintext (byte [] data) {
+ //校验响应的数据是否完整且正确 crc校验
+ int[] ints = ModbusUtil.calculateCRC(data, 0, data.length - 2);
+ if(Integer.toHexString(ints[0]).equals(Integer.toHexString(data[data.length - 2] & 0xFF)) && Integer.toHexString(ints[1]).equals(Integer.toHexString(data[data.length - 1] & 0xFF))){
+ //将寄存器值转换为可读的明文
+ return ModbusUtil.toHex(data);
+ }else{
+ return "-1";
+ }
+ }
+
+ //能量表解析 解析0000-0100的响应数据共100个寄存器的数据 16进制(浮点数) 转10进制 IEEE-754标准浮点数计算方法 浮点数 3412
+ public Integer convertTextToData (String param,String clientIp) {
+ lock.lock();
+ Integer result = 0;
+ String[] data = param.split(" ");
+ if(data[2].equals("C8")) {
+ result = energy1_100(data, clientIp);
+ }else if(data[2].equals("64")) {
+ energy101_150(data,clientIp);
+ }else {
+ System.out.println("客户端发送的指令不符合预期");
+ }
+ lock.unlock();
+ return result;
+ }
+ public Integer energy1_100(String [] data,String clientIp){
+ Integer dataCount = Integer.parseInt(data[2], 16); //数据数量
+ EnergyDataEntity energyDataEntity = new EnergyDataEntity();
+ energyDataEntity.setEnergyId(idService.gen());
+ energyDataEntity.setDeviceId(clientIp.replace(".",""));
+ energyDataEntity.setInstantaneousDelivery(hexToFloat(data[5] + data[6] + data[3] + data[4])); //瞬时流量
+ energyDataEntity.setInstantHeat(hexToFloat(data[9] + data[10] + data[7] + data[8])); //瞬时热量
+ energyDataEntity.setFluidVelocity(String.valueOf(hexToFloat(data[13] + data[14] + data[11] + data[12]))); //流体速度
+ energyDataEntity.setFluidVelocitySound(String.valueOf(hexToFloat(data[17]+data[18]+data[15]+data[16]))); //测量流体声速
+ energyDataEntity.setPositiveTotalFlow((Long.parseLong(data[21] + data[22] + data[19] + data[20],16) + hexToFloat(data[25] + data[26] + data[23] + data[24]))); //正累计流量
+ energyDataEntity.setCumulativeTraffic((Long.parseLong(data[53] + data[54] + data[51] + data[52],16) + hexToFloat(data[57] + data[58] + data[55] + data[56]))); //累计流量
+ energyDataEntity.setNegativeTotalFlow((hexToLongWithSigned(data[27] ,data[28] , data[29] , data[30]) + hexToFloat(data[33] + data[34] + data[31] + data[32]))); //负累计流量
+ energyDataEntity.setPositiveCumulativeHeat((Long.parseLong(data[37]+data[38]+data[35]+data[36],16) + hexToFloat(data[41]+data[42]+data[39]+data[40]))); //正累计热量
+ energyDataEntity.setNegativeCumulativeHeat((hexToLongWithSigned(data[43],data[44],data[45],data[46]) + hexToFloat(data[49]+data[50]+data[47]+data[48]))); //负累计热量
+ energyDataEntity.setNetCumulativeHeat((Long.parseLong(data[61]+data[62]+data[59]+data[60],16) + hexToFloat(data[65]+data[66]+data[63]+data[64])));
+ energyDataEntity.setSupplyWaterTemperature(hexToFloat(data[69] + data[70] + data[67] + data[68])); //供水温度
+ energyDataEntity.setReturnWaterTemperature(hexToFloat(data[73] + data[74] + data[71] + data[72])); //回水温度
+ energyDataEntity.setCreateTime(new Date());
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:00:00");
+ try {
+ energyDataEntity.setAcquisitionTime(formatter.parse(formatter.format(new Date())));
+ }catch (ParseException e) {
+ e.printStackTrace();
+ }
+ Integer insert = energyDataDao.insert(energyDataEntity);
+ System.out.println("已入库");
+ return insert;
+ }
+
+ public void energy101_150(String [] data, String clientIp) {
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:00:00");
+ Integer dataCount = Integer.parseInt(data[2], 16); //数据数量
+ EnergyDataEntity entity = new EnergyDataEntity();
+ entity.setDeviceId(clientIp.replace(".",""));
+ try {
+ entity.setAcquisitionTime(formatter.parse(formatter.format(new Date())));
+ }catch (ParseException e) {
+ e.printStackTrace();
+ }
+ EnergyDataEntity energyDataEntity = energyDataDao.select(entity);
+ energyDataEntity.setAccumulatedTrafficDay((Long.parseLong(data[77]+data[78]+data[75]+data[76],16) + hexToFloat(data[81]+data[82]+data[79]+data[80])));
+ energyDataEntity.setAccumulatedTrafficMonth((Long.parseLong(data[85]+data[86]+data[83]+data[84],16) + hexToFloat(data[89]+data[90]+data[87]+data[88])));
+ energyDataEntity.setAccumulatedTrafficYear((Long.parseLong(data[93]+data[94]+data[91]+data[92],16) + hexToFloat(data[97]+data[98]+data[95]+data[96])));
+ energyDataDao.updateByPrimaryKey(energyDataEntity);
+ System.out.println("已经填补剩余数据");
+ }
+
+
+ public static float hexToFloat(String hex) {
+ byte[] bytes = new byte[4];
+ for (int i = 0; i < 4; i++) {
+ bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
+ }
+ return ByteBuffer.wrap(bytes).getFloat();
+ }
+
+ //有符号型16进制转long
+ public static long hexToLongWithSigned(String a1,String a2,String a3,String a4) {
+ Integer [] arr = new Integer[2];
+ arr[0] = Integer.parseInt(a1+a2, 16);
+ arr[1] = Integer.parseInt(a3+a4, 16);
+ Arrays.sort(arr);
+ String hexStr = Integer.toHexString(arr[1]) + Integer.toHexString(arr[0]);
+ long unsignedValue = Long.parseUnsignedLong(hexStr, 16);
+ String substring = Long.toBinaryString(unsignedValue).substring(0, 1);
+ if(substring.equals("0")){
+ unsignedValue = 0;
+ } else if(substring.equals("1")){
+ unsignedValue = (long) -(Math.pow(2,32) - unsignedValue);
+ }else if(substring.equals("0")){
+ unsignedValue = (long) (Math.pow(2,32) - unsignedValue);
+ }
+ return unsignedValue;
+ }
+}
diff --git a/iot-energy/src/main/java/org/example/background/utils/FieldUtils.java b/iot-energy/src/main/java/org/example/background/utils/FieldUtils.java
new file mode 100644
index 0000000..96caa5c
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/background/utils/FieldUtils.java
@@ -0,0 +1,52 @@
+package org.example.background.utils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FieldUtils {
+ private static Pattern linePattern = Pattern.compile("_(\\w)");
+
+ /**
+ * 下划线转驼峰
+ */
+ public static String lineToCamel(String str) {
+ str = str.toLowerCase();
+ Matcher matcher = linePattern.matcher(str);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static Pattern humpPattern = Pattern.compile("[A-Z]");
+
+ /**
+ * 驼峰转下划线,效率比上面高
+ */
+ public static String camelToLine(String str) {
+ Matcher matcher = humpPattern.matcher(str);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+
+ /**
+ * 驼峰转下划线,效率比上面高
+ */
+ public static String camelToLines(String str) {
+ str = str.substring(0, 1).toLowerCase() + str.substring(1);
+ Matcher matcher = humpPattern.matcher(str);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ matcher.appendReplacement(sb, "_"+ matcher.group(0).toLowerCase());
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+}
diff --git a/iot-energy/src/main/java/org/example/druid/DBTypeEnum.java b/iot-energy/src/main/java/org/example/druid/DBTypeEnum.java
new file mode 100644
index 0000000..40cfd4c
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/druid/DBTypeEnum.java
@@ -0,0 +1,25 @@
+package org.example.druid;
+
+public enum DBTypeEnum {
+ /**
+ * 主库
+ */
+ MASTER("master"),
+
+ /**
+ * 从库
+ */
+ SLAVE("slave");
+
+ private final String value;
+
+ DBTypeEnum(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+}
+
diff --git a/iot-energy/src/main/java/org/example/druid/DbContextHolder.java b/iot-energy/src/main/java/org/example/druid/DbContextHolder.java
new file mode 100644
index 0000000..289654b
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/druid/DbContextHolder.java
@@ -0,0 +1,33 @@
+package org.example.druid;
+
+public class DbContextHolder {
+ //使用ThreadLocal,防止线程安全问题
+ private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal();
+
+ /**
+ * 设置数据源
+ *
+ * @param dbTypeEnum 数据库类型
+ */
+ public static void setDbType(DBTypeEnum dbTypeEnum) {
+ CONTEXT_HOLDER.set(dbTypeEnum.getValue());
+ }
+
+ /**
+ * 取得当前数据源
+ *
+ * @return dbType
+ */
+ public static String getDbType() {
+ return (String) CONTEXT_HOLDER.get();
+ }
+
+ /**
+ * 清除上下文数据
+ */
+ public static void clearDbType() {
+ CONTEXT_HOLDER.remove();
+ }
+
+}
+
diff --git a/iot-energy/src/main/java/org/example/druid/DruidProperties.java b/iot-energy/src/main/java/org/example/druid/DruidProperties.java
new file mode 100644
index 0000000..a807289
--- /dev/null
+++ b/iot-energy/src/main/java/org/example/druid/DruidProperties.java
@@ -0,0 +1,46 @@
+package org.example.druid;
+
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * druid 配置属性
+ */
+@Configuration
+public class DruidProperties
+{
+
+
+ @Bean
+ @ConfigurationProperties("spring.datasource.dynamic.datasource.primary")
+ public DataSource primaryDataSource()
+ {
+ return DruidDataSourceBuilder.create().build();
+ }
+
+// @Bean
+// @ConfigurationProperties(prefix = "spring.datasource.slave")
+// public DataSource slaveDataSource(){
+// return new DruidDataSourceBuilder().build();
+// }
+
+ @Bean
+ @Primary
+ public DataSource multipleDataSource(DataSource masterDataSource){
+ DynamicDataSource multipleDataSource = new DynamicDataSource();
+ Map