#update
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package com.maibu.constant;
|
||||
|
||||
public class CommandConstant {
|
||||
|
||||
public static byte remoteControl = (byte) 0x00;
|
||||
|
||||
public static byte heartbeat = (byte) 0xff;
|
||||
|
||||
public static byte path = (byte) 0x01;
|
||||
|
||||
public static byte status = (byte) 0x02;
|
||||
|
||||
public static byte connect = (byte) 0x03;
|
||||
|
||||
public static byte interaction = (byte) 0x12; //主要用于推送状态消息等
|
||||
|
||||
public static byte readConfig = (byte) 0x05; //读取配置的参数
|
||||
|
||||
public static byte writeConfig = (byte) 0x06; //写配置参数
|
||||
}
|
||||
25
maibu-common/src/main/java/com/maibu/constant/Constant.java
Normal file
25
maibu-common/src/main/java/com/maibu/constant/Constant.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.maibu.constant;
|
||||
|
||||
import com.maibu.core.business.DeviceRunParam;
|
||||
import com.maibu.core.enums.ConnectionCloseReason;
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Data
|
||||
public class Constant {
|
||||
|
||||
public static final AttributeKey<String> ATT_DEVICE_ID = AttributeKey.valueOf("deviceId");
|
||||
|
||||
public static final AttributeKey<CompletableFuture<DeviceRunParam>> READ_CONFIG_KEY =
|
||||
AttributeKey.valueOf("READ_CONFIG");
|
||||
|
||||
public static final AttributeKey<ConnectionCloseReason> ATT_CLOSE_REASON = AttributeKey.valueOf("closeReason");
|
||||
|
||||
public static final AttributeKey<String> ATT_ORG_ID = AttributeKey.valueOf("orgId");
|
||||
|
||||
public static final AttributeKey<String> ATT_SITE_ID = AttributeKey.valueOf("siteId");
|
||||
|
||||
public static final String WheelGainCoefficients = "WheelGainCoefficients";
|
||||
}
|
||||
@@ -20,21 +20,23 @@ public class DevicePlanTask extends OrgBaseDO {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long planId; //归属于某个计划任务
|
||||
private Long planId; // 归属于某个计划任务
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private DeviceTaskStaus taskStaus; //任务状态
|
||||
private DeviceTaskStaus taskStaus; // 任务状态
|
||||
|
||||
@TableField(exist = false)
|
||||
private String taskStausTranslate; //任务状态
|
||||
private String taskStausTranslate; // 任务状态
|
||||
|
||||
private Long routeId; // 线路id
|
||||
|
||||
private LocalDateTime finishTime;
|
||||
|
||||
private LocalDateTime startTime; //开始时间 时分秒
|
||||
private LocalDateTime startTime; // 开始时间 时分秒
|
||||
|
||||
private String taskType; //任务类型分类
|
||||
private String taskType; // 任务类型分类
|
||||
|
||||
private LocalDateTime canExecuteTime; // 这个时间之前都不能执行
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package com.maibu.core.business;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.maibu.constant.CommandConstant;
|
||||
import com.maibu.core.domain.BaseDO;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@TableName(value = "iot_device_run_param", autoResultMap = true)
|
||||
@@ -22,6 +28,432 @@ public class DeviceRunParam extends BaseDO {
|
||||
|
||||
private Long orgId;
|
||||
|
||||
private Double runSpeed;
|
||||
private Integer runSpeed; //不下发到设备自己用
|
||||
|
||||
|
||||
@TableField(exist = false)
|
||||
public byte header1;
|
||||
@TableField(exist = false)
|
||||
public byte header2;
|
||||
@TableField(exist = false)
|
||||
public byte cmd;
|
||||
|
||||
|
||||
public byte chipUidSign;//芯片UID固化标志位
|
||||
public String chipUid;//芯片UID
|
||||
|
||||
public byte[] remoteConfig;//遥控器通道配置
|
||||
public int knifeMotorMode;//割刀电机模式
|
||||
public int walkMotorMode;//行走电机模式
|
||||
public int leftMotorReverse;//左轮电机方向极性
|
||||
public int rightMotorReverse;//右轮电机方向极性
|
||||
public int swapChannel;//左右轮通道互换标志位
|
||||
public int use4G;//联网标志位
|
||||
|
||||
public short forwardSpeedLimit;//前进速度限制
|
||||
public short turnSpeedLimit;//转向速度限制
|
||||
|
||||
public int knifePolarity;//割刀通道极性
|
||||
public int fanPolarity;//风门通道极性
|
||||
public int throttlePolarity;//油门通道极性
|
||||
public int liftProtectTime;//底盘升降电机保护时间
|
||||
public int dualRtk;//RTK配置
|
||||
|
||||
|
||||
public int knifeChannel;//割刀通道配置
|
||||
public int fanChannel;//风门通道配置
|
||||
|
||||
public int throttleChannel;//油门通道配置
|
||||
public int remoteType;//遥控器类型
|
||||
public boolean relayBoard;//是否搭载继电器板
|
||||
|
||||
public int liftChannel;//底盘升通道配置
|
||||
public int chassisChannel;//底盘降通道配置
|
||||
|
||||
public int armChannel;//抱闸通道配置
|
||||
public int fuelPumpChannel;//燃油励磁通道配置
|
||||
|
||||
public String wifiName;//wifi名称
|
||||
public String wifiPassword;//wifi密码
|
||||
|
||||
public int batteryType;//电池类型
|
||||
public int driveType;//行走驱动配置
|
||||
|
||||
public float gearRatio;//转速比
|
||||
public float robotLength;//机器人长度
|
||||
public float robotWidth;//机器人宽度
|
||||
public float robotHeight;//机器人高度
|
||||
public float knifeWidth;//割刀宽度
|
||||
public float tyreSize;//轮胎尺寸
|
||||
public float leftForwardGain;//左轮前进增益
|
||||
public float leftBackwardGain;//左轮后退增益
|
||||
public float rightForwardGain;//右轮前进增益
|
||||
public float rightBackwardGain;//右轮后退增益
|
||||
|
||||
public String firmwareVersion;//固件版本
|
||||
@TableField(exist = false)
|
||||
public int crc16;
|
||||
@TableField(exist = false)
|
||||
public byte tail1;
|
||||
@TableField(exist = false)
|
||||
public byte tail2;
|
||||
@TableField(exist = false)
|
||||
private byte[] originalData;
|
||||
|
||||
public static byte[] toReadBytes() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(7); // 修改为27字节
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buffer.put((byte) 0xAB);
|
||||
buffer.put((byte) 0xAA);
|
||||
|
||||
buffer.put(CommandConstant.readConfig);
|
||||
|
||||
buffer.putShort((short) 0);
|
||||
|
||||
buffer.put((byte) 0xAA);
|
||||
buffer.put((byte) 0xAB);
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
// public static byte[] toWriteBytes(DeviceRunParam p) {
|
||||
//
|
||||
// ByteBuffer buffer = ByteBuffer.allocate(178);
|
||||
// buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
//
|
||||
// // 包头
|
||||
// buffer.put((byte) 0xAB);
|
||||
// buffer.put((byte) 0xAA);
|
||||
//
|
||||
// // 指令
|
||||
// buffer.put(CommandConstant.writeConfig);
|
||||
//
|
||||
// // UID固化标志
|
||||
// buffer.put(p.getChipUidSign());
|
||||
//
|
||||
// // UID
|
||||
// putFixedString(buffer, p.getChipUid(), 45);
|
||||
//
|
||||
// // Byte68
|
||||
// byte remote = 0;
|
||||
// remote |= (p.getKnifeMotorMode() & 0x03);
|
||||
// remote |= (p.getWalkMotorMode() & 0x03) << 2;
|
||||
// remote |= (p.getLeftMotorReverse() & 0x01) << 4;
|
||||
// remote |= (p.getRightMotorReverse() & 0x01) << 5;
|
||||
// remote |= (p.getSwapChannel() & 0x01) << 6;
|
||||
// remote |= (p.getUse4G() & 0x01) << 7;
|
||||
// buffer.put(remote);
|
||||
//
|
||||
// // 69~72
|
||||
// buffer.putShort((short) p.getForwardSpeedLimit());
|
||||
// buffer.putShort((short) p.getTurnSpeedLimit());
|
||||
//
|
||||
// //73
|
||||
// byte b73 = 0;
|
||||
// b73 |= (p.getKnifePolarity() & 1);
|
||||
// b73 |= (p.getFanPolarity() & 1) << 1;
|
||||
// b73 |= (p.getThrottlePolarity() & 1) << 2;
|
||||
// b73 |= (p.getLiftProtectTime() & 0x0F) << 3;
|
||||
// b73 |= (p.getDualRtk() & 1) << 7;
|
||||
// buffer.put(b73);
|
||||
//
|
||||
// //74
|
||||
// byte b74 = 0;
|
||||
// b74 |= (p.getKnifeChannel() & 0x0F);
|
||||
// b74 |= (p.getFanChannel() & 0x0F) << 4;
|
||||
// buffer.put(b74);
|
||||
//
|
||||
// //75
|
||||
// byte b75 = 0;
|
||||
// b75 |= (p.getThrottleChannel() & 0x0F);
|
||||
// b75 |= (p.getRemoteType() & 0x07) << 4;
|
||||
// if (p.isRelayBoard()) {
|
||||
// b75 |= 1 << 7;
|
||||
// }
|
||||
// buffer.put(b75);
|
||||
//
|
||||
// //76
|
||||
// byte b76 = 0;
|
||||
// b76 |= (p.getLiftChannel() & 0x0F);
|
||||
// b76 |= (p.getChassisChannel() & 0x0F) << 4;
|
||||
// buffer.put(b76);
|
||||
//
|
||||
// //77
|
||||
// byte b77 = 0;
|
||||
// b77 |= (p.getArmChannel() & 0x0F);
|
||||
// b77 |= (p.getFuelPumpChannel() & 0x0F) << 4;
|
||||
// buffer.put(b77);
|
||||
//
|
||||
// //wifi
|
||||
// putFixedString(buffer, p.getWifiName(), 20);
|
||||
// putFixedString(buffer, p.getWifiPassword(), 20);
|
||||
//
|
||||
// //118
|
||||
// byte b118 = 0;
|
||||
// b118 |= (p.getBatteryType() & 0x03);
|
||||
// b118 |= (p.getDriveType() & 0x03) << 2;
|
||||
// buffer.put(b118);
|
||||
//
|
||||
// //119~158 float
|
||||
// buffer.putFloat(p.getGearRatio());
|
||||
// buffer.putFloat(p.getRobotLength());
|
||||
// buffer.putFloat(p.getRobotWidth());
|
||||
// buffer.putFloat(p.getRobotHeight());
|
||||
// buffer.putFloat(p.getKnifeWidth());
|
||||
// buffer.putFloat(p.getTyreSize());
|
||||
// buffer.putFloat(p.getLeftForwardGain());
|
||||
// buffer.putFloat(p.getLeftBackwardGain());
|
||||
// buffer.putFloat(p.getRightForwardGain());
|
||||
// buffer.putFloat(p.getRightBackwardGain());
|
||||
//
|
||||
// //159~173
|
||||
// putFixedString(buffer, p.getFirmwareVersion(), 15);
|
||||
//
|
||||
// //CRC
|
||||
//
|
||||
// / / byte[] bytes = buffer.array();
|
||||
// / / int crc = CRC16Util.crc16(bytes, 0, 174);
|
||||
// buffer.putShort((short) 0);
|
||||
//
|
||||
// //Tail
|
||||
// buffer.put((byte) 0xAA);
|
||||
// buffer.put((byte) 0xAB);
|
||||
//
|
||||
// return buffer.array();
|
||||
// }
|
||||
|
||||
public byte[] toWriteBytes() {
|
||||
// 如果没有原始数据,使用静态方法重新构建
|
||||
if (originalData == null || originalData.length < 178) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 克隆原始数据,避免修改原始数据
|
||||
byte[] result = originalData.clone();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(result);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// 修改指令为写配置(字节2)
|
||||
buffer.put(2, CommandConstant.writeConfig);
|
||||
|
||||
// 修改字节68:遥控器配置
|
||||
byte remote = 0;
|
||||
remote |= (getKnifeMotorMode() & 0x03);
|
||||
remote |= (getWalkMotorMode() & 0x03) << 2;
|
||||
remote |= (getLeftMotorReverse() & 0x01) << 4;
|
||||
remote |= (getRightMotorReverse() & 0x01) << 5;
|
||||
remote |= (getSwapChannel() & 0x01) << 6;
|
||||
remote |= (getUse4G() & 0x01) << 7;
|
||||
buffer.put(68, remote);
|
||||
|
||||
// 修改字节69-72:速度限制
|
||||
buffer.putShort(69, getForwardSpeedLimit());
|
||||
buffer.putShort(71, getTurnSpeedLimit());
|
||||
|
||||
// 修改字节73:通道极性
|
||||
byte b73 = 0;
|
||||
b73 |= (getKnifePolarity() & 1);
|
||||
b73 |= (getFanPolarity() & 1) << 1;
|
||||
b73 |= (getThrottlePolarity() & 1) << 2;
|
||||
b73 |= (getLiftProtectTime() & 0x0F) << 3;
|
||||
b73 |= (getDualRtk() & 1) << 7;
|
||||
buffer.put(73, b73);
|
||||
|
||||
// 修改字节74-77:通道配置
|
||||
byte b74 = 0;
|
||||
b74 |= (getKnifeChannel() & 0x0F);
|
||||
b74 |= (getFanChannel() & 0x0F) << 4;
|
||||
buffer.put(74, b74);
|
||||
|
||||
byte b75 = 0;
|
||||
b75 |= (getThrottleChannel() & 0x0F);
|
||||
b75 |= (getRemoteType() & 0x07) << 4;
|
||||
if (isRelayBoard()) b75 |= 1 << 7;
|
||||
buffer.put(75, b75);
|
||||
|
||||
byte b76 = 0;
|
||||
b76 |= (getLiftChannel() & 0x0F);
|
||||
b76 |= (getChassisChannel() & 0x0F) << 4;
|
||||
buffer.put(76, b76);
|
||||
|
||||
byte b77 = 0;
|
||||
b77 |= (getArmChannel() & 0x0F);
|
||||
b77 |= (getFuelPumpChannel() & 0x0F) << 4;
|
||||
buffer.put(77, b77);
|
||||
|
||||
// // 修改字节78-117:WiFi信息
|
||||
// byte[] wifiNameBytes = new byte[20];
|
||||
// if (getWifiName() != null) {
|
||||
// byte[] src = getWifiName().getBytes(StandardCharsets.UTF_8);
|
||||
// System.arraycopy(src, 0, wifiNameBytes, 0, Math.min(src.length, 20));
|
||||
// }
|
||||
// System.arraycopy(wifiNameBytes, 0, result, 78, 20);
|
||||
//
|
||||
// byte[] wifiPwdBytes = new byte[20];
|
||||
// if (getWifiPassword() != null) {
|
||||
// byte[] src = getWifiPassword().getBytes(StandardCharsets.UTF_8);
|
||||
// System.arraycopy(src, 0, wifiPwdBytes, 0, Math.min(src.length, 20));
|
||||
// }
|
||||
// System.arraycopy(wifiPwdBytes, 0, result, 98, 20);
|
||||
|
||||
// // 修改字节118:电池和驱动配置
|
||||
// byte b118 = 0;
|
||||
// b118 |= (getBatteryType() & 0x03);
|
||||
// b118 |= (getDriveType() & 0x03) << 2;
|
||||
// buffer.put(118, b118);
|
||||
|
||||
// 修改字节119-158:float参数
|
||||
buffer.putFloat(119, getGearRatio());
|
||||
buffer.putFloat(123, getRobotLength());
|
||||
buffer.putFloat(127, getRobotWidth());
|
||||
buffer.putFloat(131, getRobotHeight());
|
||||
buffer.putFloat(135, getKnifeWidth());
|
||||
buffer.putFloat(139, getTyreSize());
|
||||
buffer.putFloat(143, getLeftForwardGain());
|
||||
buffer.putFloat(147, getLeftBackwardGain());
|
||||
buffer.putFloat(151, getRightForwardGain());
|
||||
buffer.putFloat(155, getRightBackwardGain());
|
||||
|
||||
// 修改字节159-173:固件版本
|
||||
// byte[] fwBytes = new byte[15];
|
||||
// if (getFirmwareVersion() != null) {
|
||||
// byte[] src = getFirmwareVersion().getBytes(StandardCharsets.UTF_8);
|
||||
// System.arraycopy(src, 0, fwBytes, 0, Math.min(src.length, 15));
|
||||
// }
|
||||
// System.arraycopy(fwBytes, 0, result, 159, 15);
|
||||
|
||||
// 保留原始的 CRC 和尾部,不强制修改
|
||||
// 移除以下强制修改:
|
||||
// buffer.putShort(174, (short) 0);
|
||||
// buffer.put(176, (byte) 0xAA);
|
||||
// buffer.put(177, (byte) 0xAB);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void putFixedString(ByteBuffer buffer, String value, int len) {
|
||||
|
||||
byte[] bytes = new byte[len];
|
||||
|
||||
if (value != null) {
|
||||
byte[] src = value.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
System.arraycopy(src, 0, bytes, 0, Math.min(src.length, len));
|
||||
}
|
||||
|
||||
buffer.put(bytes);
|
||||
}
|
||||
|
||||
|
||||
public static DeviceRunParam fromBytes(byte[] data) {
|
||||
// 183
|
||||
if (data == null || data.length < 178) {
|
||||
throw new IllegalArgumentException("数据长度不足");
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
DeviceRunParam p = new DeviceRunParam();
|
||||
|
||||
p.setOriginalData(data);
|
||||
|
||||
p.setHeader1(buffer.get());
|
||||
p.setHeader2(buffer.get());
|
||||
|
||||
p.setCmd(buffer.get());
|
||||
|
||||
p.setChipUidSign(buffer.get());
|
||||
|
||||
p.setChipUid(readString(buffer, 45));
|
||||
|
||||
// 跳过字节49-67(共19字节,预留不用)
|
||||
buffer.position(buffer.position() + 19);
|
||||
|
||||
byte remote = buffer.get();
|
||||
|
||||
// p.setRemoteConfig(remote);
|
||||
|
||||
p.setKnifeMotorMode(remote & 0x03);
|
||||
p.setWalkMotorMode((remote >> 2) & 0x03);
|
||||
p.setLeftMotorReverse((remote >> 4) & 0x01);
|
||||
p.setRightMotorReverse((remote >> 5) & 0x01);
|
||||
p.setSwapChannel((remote >> 6) & 0x01);
|
||||
p.setUse4G((remote >> 7) & 0x01);
|
||||
|
||||
p.setForwardSpeedLimit(buffer.getShort());
|
||||
p.setTurnSpeedLimit(buffer.getShort());
|
||||
|
||||
byte b73 = buffer.get();
|
||||
|
||||
p.setKnifePolarity(b73 & 1);
|
||||
p.setFanPolarity((b73 >> 1) & 1);
|
||||
p.setThrottlePolarity((b73 >> 2) & 1);
|
||||
p.setLiftProtectTime((b73 >> 3) & 0x0F);
|
||||
p.setDualRtk((b73 >> 7) & 1);
|
||||
|
||||
byte b74 = buffer.get();
|
||||
|
||||
p.setKnifeChannel(b74 & 0x0F);
|
||||
p.setFanChannel((b74 >> 4) & 0x0F);
|
||||
|
||||
byte b75 = buffer.get();
|
||||
|
||||
p.setThrottleChannel(b75 & 0x0F);
|
||||
p.setRemoteType((b75 >> 4) & 0x07);
|
||||
p.setRelayBoard(((b75 >> 7) & 1) == 1);
|
||||
|
||||
byte b76 = buffer.get();
|
||||
|
||||
p.setLiftChannel(b76 & 0x0F);
|
||||
p.setChassisChannel((b76 >> 4) & 0x0F);
|
||||
|
||||
byte b77 = buffer.get();
|
||||
|
||||
p.setArmChannel(b77 & 0x0F);
|
||||
p.setFuelPumpChannel((b77 >> 4) & 0x0F);
|
||||
|
||||
p.setWifiName(readString(buffer, 20));
|
||||
p.setWifiPassword(readString(buffer, 20));
|
||||
|
||||
byte b118 = buffer.get();
|
||||
|
||||
p.setBatteryType(b118 & 0x03);
|
||||
p.setDriveType((b118 >> 2) & 0x03);
|
||||
|
||||
// float
|
||||
p.setGearRatio(buffer.getFloat());
|
||||
p.setRobotLength(buffer.getFloat());
|
||||
p.setRobotWidth(buffer.getFloat());
|
||||
p.setRobotHeight(buffer.getFloat());
|
||||
p.setKnifeWidth(buffer.getFloat());
|
||||
p.setTyreSize(buffer.getFloat());
|
||||
p.setLeftForwardGain(buffer.getFloat());
|
||||
p.setLeftBackwardGain(buffer.getFloat());
|
||||
p.setRightForwardGain(buffer.getFloat());
|
||||
p.setRightBackwardGain(buffer.getFloat());
|
||||
|
||||
p.setFirmwareVersion(readString(buffer, 15));
|
||||
|
||||
p.setCrc16(Short.toUnsignedInt(buffer.getShort()));
|
||||
|
||||
p.setTail1(buffer.get());
|
||||
p.setTail2(buffer.get());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
public static String readString(ByteBuffer buffer, int len) {
|
||||
byte[] bytes = new byte[len];
|
||||
buffer.get(bytes);
|
||||
|
||||
int end = 0;
|
||||
while (end < bytes.length && bytes[end] != 0) {
|
||||
end++;
|
||||
}
|
||||
|
||||
return new String(bytes, 0, end, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,10 +2,14 @@ package com.maibu.core.business;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.maibu.core.domain.OrgNoUserDO;
|
||||
import com.maibu.core.enums.ErrorLevel;
|
||||
import com.maibu.core.enums.ErrorSource;
|
||||
@@ -15,10 +19,11 @@ import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("iot_work_order")
|
||||
@TableName(value = "iot_work_order", autoResultMap = true)
|
||||
@ApiModel("工单记录")
|
||||
public class IOTWorkOrder extends OrgNoUserDO {
|
||||
|
||||
@@ -64,21 +69,31 @@ public class IOTWorkOrder extends OrgNoUserDO {
|
||||
private String collaboratorNames;
|
||||
|
||||
@ApiModelProperty("计划开始时间")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime planStartTime;
|
||||
|
||||
@ApiModelProperty("计划结束时间")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime planEndTime;
|
||||
|
||||
@ApiModelProperty("截止时间")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime deadlineTime;
|
||||
|
||||
@ApiModelProperty("实际开始时间")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime actualStartTime;
|
||||
|
||||
@ApiModelProperty("实际完成时间")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime actualEndTime;
|
||||
|
||||
@ApiModelProperty("工单状态:1-待处理 2-已派发 3-执行中 4-已完成 5-已挂起")
|
||||
@ApiModelProperty("工单状态:1-待处理 2-执行中 3-已完成 4-已挂起")
|
||||
private Integer orderStatus;
|
||||
|
||||
@ApiModelProperty("关联告警 ID")
|
||||
@@ -105,7 +120,23 @@ public class IOTWorkOrder extends OrgNoUserDO {
|
||||
@ApiModelProperty("处理备注")
|
||||
private String handleRemark;
|
||||
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> imgUrl; //创建时候的照片
|
||||
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> videoUrl;//创建时候的视频
|
||||
|
||||
private Long modelId;
|
||||
|
||||
private Long taskId;
|
||||
private String taskId;
|
||||
|
||||
private boolean autoCreate = false; //自动创建
|
||||
|
||||
private String failureCause; //故障原因
|
||||
|
||||
private String handleMeasures; //处理措施
|
||||
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> images; //现场图片 处理后的照片
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.maibu.core.business.alarm_center;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.maibu.annotation.Excel;
|
||||
import com.maibu.core.domain.TenantBaseDO;
|
||||
import com.maibu.core.enums.AlarmHandleResult;
|
||||
import com.maibu.core.enums.AlarmType;
|
||||
import com.maibu.core.enums.ErrorLevel;
|
||||
import com.maibu.core.enums.ErrorSource;
|
||||
|
||||
@@ -34,7 +36,7 @@ public class Alarm extends TenantBaseDO {
|
||||
|
||||
@ApiModelProperty("告警类型:1-设备故障 2-数据异常 3-通信中断 4-性能告警 5-安全告警 6-环境告警")
|
||||
@Excel(name = "告警类型", readConverterExp = "1=设备故障,2=数据异常,3=通信中断,4=性能告警,5=安全告警,6=环境告警")
|
||||
private Integer alarmType;
|
||||
private AlarmType alarmType;
|
||||
|
||||
@ApiModelProperty("告警标题")
|
||||
@Excel(name = "告警标题")
|
||||
@@ -44,6 +46,8 @@ public class Alarm extends TenantBaseDO {
|
||||
@Excel(name = "告警内容")
|
||||
private String alarmContent;
|
||||
|
||||
private String timeValues; //告警时上报的值
|
||||
|
||||
@ApiModelProperty("设备ID")
|
||||
@Excel(name = "设备ID", cellType = Excel.ColumnType.NUMERIC)
|
||||
private String deviceId;
|
||||
@@ -68,8 +72,8 @@ public class Alarm extends TenantBaseDO {
|
||||
@Excel(name = "持续时长(秒)", cellType = Excel.ColumnType.NUMERIC)
|
||||
private Integer duration;
|
||||
|
||||
@ApiModelProperty("处理状态:1-待处理 2-处理中 3-已关闭 4-已忽略")
|
||||
@Excel(name = "处理状态", readConverterExp = "1=待处理,2=处理中,3=已关闭,4=已忽略")
|
||||
@ApiModelProperty("处理状态:1-待处理 2已关闭")
|
||||
@Excel(name = "处理状态", readConverterExp = "1=待处理,2=已关闭")
|
||||
private Integer handleStatus;
|
||||
|
||||
@ApiModelProperty("处理人ID")
|
||||
@@ -111,4 +115,6 @@ public class Alarm extends TenantBaseDO {
|
||||
private Long siteId;
|
||||
|
||||
private Long orgId;
|
||||
|
||||
private AlarmHandleResult handleResult;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.maibu.core.enums.ErrorLevel;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
@Data
|
||||
public class AlarmMessage {
|
||||
@@ -21,6 +22,7 @@ public class AlarmMessage {
|
||||
|
||||
/** 告警时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime time;
|
||||
|
||||
/** 设备名称 */
|
||||
@@ -45,4 +47,6 @@ public class AlarmMessage {
|
||||
|
||||
private Long orgId;
|
||||
|
||||
private String timeValues;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.maibu.core.business.device;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.maibu.utils.json.JsonUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
@@ -74,7 +77,7 @@ public class NettyDevice extends Connector {
|
||||
this.deviceType = deviceType;
|
||||
}
|
||||
|
||||
// todo 重发
|
||||
// todo 如何确定命令已收到?
|
||||
public void sendNextPoint() {
|
||||
if (task.getTaskStaus().equals(DeviceTaskStaus.PAUSE))
|
||||
return;
|
||||
@@ -139,9 +142,9 @@ public class NettyDevice extends Connector {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void sendPathCommand(byte coommadnType, short pointCounts, double lat, double lng, short speed) {
|
||||
public void sendPathCommand(byte commandType, short pointCounts, double lat, double lng, short speed) {
|
||||
RoutePlanSendEntity routePlanSendEntity = new RoutePlanSendEntity();
|
||||
routePlanSendEntity.setCommandType(coommadnType);
|
||||
routePlanSendEntity.setCommandType(commandType);
|
||||
routePlanSendEntity.setPointCounts(pointCounts);
|
||||
routePlanSendEntity.setTargetLatitude(lat);
|
||||
routePlanSendEntity.setTargetLongitude(lng);
|
||||
@@ -153,6 +156,57 @@ public class NettyDevice extends Connector {
|
||||
}
|
||||
}
|
||||
|
||||
//配置左右轮增益系数
|
||||
public void sendWheelGainCoefficientsCommand(double leftForwardGain, double leftBackwardGain, double rightForwardGain, double rightBackwardGain) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(39);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buffer.put((byte) 0xAB);
|
||||
buffer.put((byte) 0xAA);
|
||||
|
||||
buffer.put((byte) 0x13);
|
||||
|
||||
long leftForward = Double.doubleToLongBits(leftForwardGain);
|
||||
buffer.putLong(leftForward);
|
||||
|
||||
long leftBackward = Double.doubleToLongBits(leftBackwardGain);
|
||||
buffer.putLong(leftBackward);
|
||||
|
||||
long rightForward = Double.doubleToLongBits(rightForwardGain);
|
||||
buffer.putLong(rightForward);
|
||||
|
||||
long rightBackward = Double.doubleToLongBits(rightBackwardGain);
|
||||
buffer.putLong(rightBackward);
|
||||
|
||||
buffer.putShort((short) 0);
|
||||
|
||||
buffer.put((byte) 0xAA);
|
||||
buffer.put((byte) 0xAB);
|
||||
|
||||
log.debug("下发设置左右轮增益系数报文:{}", toHexString(buffer.array()));
|
||||
if (this.getChannel() != null && this.getChannel().isActive()) {
|
||||
this.getChannel().writeAndFlush(Unpooled.wrappedBuffer(buffer.array()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void readConfigCommand() {
|
||||
byte[] bytes = DeviceRunParam.toReadBytes();
|
||||
log.debug("下发读取配置的报文:{}", toHexString(bytes));
|
||||
if (this.getChannel() != null && this.getChannel().isActive()) {
|
||||
this.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeConfigCommand(DeviceRunParam deviceRunParam) {
|
||||
log.debug("下发修改配置的属性:{}", JsonUtils.toJsonString(deviceRunParam));
|
||||
byte[] bytes = deviceRunParam.toWriteBytes();
|
||||
log.debug("下发修改配置的报文:{}", toHexString(bytes));
|
||||
if (this.getChannel() != null && this.getChannel().isActive()) {
|
||||
this.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
// todo 优化
|
||||
public synchronized boolean pauseTask() {
|
||||
if (executingTask) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.maibu.core.business.dto;
|
||||
|
||||
import com.maibu.core.enums.AlarmHandleResult;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
@@ -11,7 +12,7 @@ public class AlarmHandleDTO {
|
||||
@ApiModelProperty("告警ID")
|
||||
private Long alarmId;
|
||||
|
||||
@ApiModelProperty("处理状态:2-处理中 3-已关闭 4-已忽略")
|
||||
@ApiModelProperty("处理状态:1-待处理 2已关闭")
|
||||
private Integer handleStatus;
|
||||
|
||||
@ApiModelProperty("处理备注")
|
||||
@@ -19,4 +20,7 @@ public class AlarmHandleDTO {
|
||||
|
||||
@ApiModelProperty("根因分析")
|
||||
private String rootCause;
|
||||
|
||||
@ApiModelProperty("处理结果")
|
||||
private AlarmHandleResult handleResult;
|
||||
}
|
||||
|
||||
@@ -39,4 +39,6 @@ public class AlarmQueryDTO extends BaseEntity {
|
||||
|
||||
@ApiModelProperty("场站ID")
|
||||
private Long siteId;
|
||||
|
||||
private Integer handleStatus;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import javax.lang.model.type.ErrorType;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.maibu.core.enums.ErrorLevel;
|
||||
import com.maibu.core.enums.OrderType;
|
||||
|
||||
@Data
|
||||
@@ -27,7 +29,7 @@ public class WorkOrderQueryDTO {
|
||||
private OrderType orderType;
|
||||
|
||||
@ApiModelProperty("优先级")
|
||||
private ErrorType priorityLevel;
|
||||
private ErrorLevel priorityLevel;
|
||||
|
||||
@ApiModelProperty("负责人 ID")
|
||||
private Long assigneeId;
|
||||
@@ -44,10 +46,12 @@ public class WorkOrderQueryDTO {
|
||||
@ApiModelProperty("工单状态")
|
||||
private Integer orderStatus;
|
||||
|
||||
@ApiModelProperty("开始时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@ApiModelProperty("结束时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@ApiModelProperty("搜索关键词")
|
||||
|
||||
@@ -15,12 +15,14 @@ public class MowerCommandDispather {
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
public void dispathDeviceTask(DeviceTaskCommandDTO dto) {
|
||||
public Long dispathDeviceTask(DeviceTaskCommandDTO dto) {
|
||||
Map<String, MowerCommandInter> beanMap = applicationContext.getBeansOfType(MowerCommandInter.class);
|
||||
// 遍历
|
||||
beanMap.values().forEach(x -> {
|
||||
x.createDeviceTask(dto);
|
||||
});
|
||||
Long id = null;
|
||||
for (MowerCommandInter value : beanMap.values()) {
|
||||
id = value.createDeviceTask(dto);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import com.maibu.core.business.dto.DeviceTaskCommandDTO;
|
||||
|
||||
public interface MowerCommandInter {
|
||||
|
||||
void createDeviceTask(DeviceTaskCommandDTO dto);
|
||||
Long createDeviceTask(DeviceTaskCommandDTO dto);
|
||||
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ public class UAVCommandDispather {
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
public void dispathFightTask(FlightTaskCreateDTO dto) {
|
||||
public String dispathFightTask(FlightTaskCreateDTO dto) {
|
||||
Map<String, UAVCommandInter> beanMap = applicationContext.getBeansOfType(UAVCommandInter.class);
|
||||
// 遍历
|
||||
beanMap.values().forEach(x -> {
|
||||
x.createFlightTask(dto);
|
||||
});
|
||||
String taskId = null;
|
||||
for (UAVCommandInter value : beanMap.values()) {
|
||||
taskId = value.createFlightTask(dto);
|
||||
}
|
||||
return taskId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import com.maibu.core.business.dto.uav.FlightTaskCreateDTO;
|
||||
|
||||
public interface UAVCommandInter {
|
||||
|
||||
void createFlightTask(FlightTaskCreateDTO dto);
|
||||
String createFlightTask(FlightTaskCreateDTO dto);
|
||||
|
||||
}
|
||||
|
||||
@@ -41,4 +41,6 @@ public class SysSite extends BaseDO {
|
||||
|
||||
private String dingTalkToken; // 钉钉推送token
|
||||
|
||||
private boolean dingTalkOrderPush = false;// 工单是否推送
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.maibu.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AlarmHandleResult {
|
||||
|
||||
Handled("Handled", "已处理"),
|
||||
|
||||
Restored("Restored", "已恢复"),
|
||||
|
||||
Ignored("Ignored", "已忽略"),
|
||||
|
||||
UnableToHandle("UnableToHandle", "无法处理"),
|
||||
|
||||
ManufacturerHandling("ManufacturerHandling", "厂家处理中"),
|
||||
|
||||
FalseAlarm("FalseAlarm", "误报");
|
||||
|
||||
/**
|
||||
* 错误来源值(存储在数据库中)
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 错误来源名称(用于前端显示)
|
||||
*/
|
||||
private final String name;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.maibu.core.enums;
|
||||
|
||||
import com.maibu.annotation.Excel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AlarmType {
|
||||
|
||||
UAV_FAULT("UAV_FAULT", "无人机故障"),
|
||||
|
||||
MOWER_FAULT("MOWER_FAULT", "割草机故障"),
|
||||
|
||||
OTHER_DEVICE_FAULT("OTHER_DEVICE_FAULT", "其他设备故障"),
|
||||
|
||||
SERVER_FAILURE("SERVER_FAILURE", "服务器故障"),
|
||||
|
||||
SYSTEM_ERROR("SYSTEM_ERROR", "系统错误");
|
||||
|
||||
/** 错误来源值(存储在数据库中) */
|
||||
private final String value;
|
||||
|
||||
/** 错误来源名称(用于前端显示) */
|
||||
private final String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.maibu.memory;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.maibu.core.business.device.Connector;
|
||||
import com.maibu.core.business.device.NettyDevice;
|
||||
import com.maibu.core.enums.ConnectorStatus;
|
||||
import com.maibu.core.enums.ConnectorType;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 设备会话管理器
|
||||
* 负责管理设备信息、连接通道、双向绑定关系、状态管理和消息转发
|
||||
*/
|
||||
@Component
|
||||
@Data
|
||||
public class DeviceSessionManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DeviceSessionManager.class);
|
||||
// 设备ID -> 设备对象(业务实体)
|
||||
private final ConcurrentHashMap<String, NettyDevice> deviceMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 设备ID -> Channel(通信通道)
|
||||
private final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
|
||||
|
||||
// Channel -> 设备ID(反查)
|
||||
private final ConcurrentHashMap<Channel, String> channelToDeviceIdMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public void clearChannelToDeviceIdMap(String deviceId) {
|
||||
if (StringUtils.isEmpty(deviceId) || CollectionUtils.isEmpty(channelToDeviceIdMap)) {
|
||||
return;
|
||||
}
|
||||
// 获取 entrySet 迭代器,安全遍历+删除
|
||||
Iterator<Map.Entry<Channel, String>> iterator = channelToDeviceIdMap.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<Channel, String> entry = iterator.next();
|
||||
Channel channel = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
// 匹配目标 deviceId
|
||||
if (deviceId.equals(value)) {
|
||||
// 关闭 Channel:捕获 IO 异常,避免单个 Channel 关闭失败影响整体遍历
|
||||
if (channel != null && channel.isActive()) {
|
||||
try {
|
||||
channel.close().syncUninterruptibly(); // 优雅关闭,等待关闭完成
|
||||
} catch (Exception e) {
|
||||
// 记录日志,不中断后续操作
|
||||
logger.error("关闭 Channel 失败", e);
|
||||
}
|
||||
}
|
||||
// 关键:使用迭代器的 remove() 方法,安全移除键值对,不会触发并发修改异常
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 注册设备连接(设备上线)
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param channel 通信通道
|
||||
* @param deviceType 设备类型(MASTER/SLAVE)
|
||||
*/
|
||||
public void registerDevice(String deviceId, Channel channel, ConnectorType deviceType, ConnectorStatus status, String slaveId) {
|
||||
NettyDevice nettyDevice = deviceMap.computeIfAbsent(deviceId, id -> new NettyDevice(id, deviceType));
|
||||
nettyDevice.setChannel(channel);
|
||||
nettyDevice.setConnectorStatus(status); // 默认上线即ACTIVE
|
||||
nettyDevice.setCurrentSlaveId(slaveId);
|
||||
nettyDevice.setLatestLoginTime(System.currentTimeMillis());
|
||||
deviceMap.put(deviceId, nettyDevice);
|
||||
channelMap.put(deviceId, channel);
|
||||
channelToDeviceIdMap.put(channel, deviceId);
|
||||
System.out.println("Registered device: " + deviceId + " type: " + deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上位机绑定下位机,建立双向绑定关系
|
||||
*
|
||||
* @param masterId 上位机设备ID
|
||||
* @param slaveId 下位机设备ID
|
||||
*/
|
||||
public void bindConnector(String masterId, String slaveId) {
|
||||
NettyDevice master = deviceMap.get(masterId);
|
||||
if (master == null) return;
|
||||
NettyDevice slave = deviceMap.computeIfAbsent(slaveId, id -> new NettyDevice(id, ConnectorType.SLAVE));
|
||||
master.addConnectedDevice(slave);
|
||||
System.out.println("Bind connectors for master " + masterId + " and slave " + slaveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备ID获取设备对象
|
||||
*/
|
||||
public NettyDevice getDevice(String deviceId) {
|
||||
return deviceMap.get(deviceId);
|
||||
}
|
||||
|
||||
// 模糊匹配
|
||||
public List<NettyDevice> getDevicesLike(String query) {
|
||||
return deviceMap.values().stream()
|
||||
.filter(device -> device.getConnectorId().contains(query))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
public List<NettyDevice> getSlaveControl(String slaveId) {
|
||||
// 获取所有连接的主机 且是active状态的
|
||||
return deviceMap.values().stream()
|
||||
.filter(device -> ObjectUtil.equals(device.getCurrentSlaveId(), slaveId)
|
||||
&& ConnectorStatus.ACTIVE.equals(device.getConnectorStatus()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
public List<NettyDevice> getAllSlaveControl(String slaveId) {
|
||||
// 获取所有连接的主机 不区分状态
|
||||
return deviceMap.values().stream()
|
||||
.filter(device -> ObjectUtil.equals(device.getCurrentSlaveId(), slaveId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public NettyDevice getCurrentControl(String slaveId) {
|
||||
// 获取所有连接的主机 不区分状态
|
||||
return deviceMap.values().stream()
|
||||
.filter(device -> ObjectUtil.equals(device.getCurrentSlaveId(), slaveId)
|
||||
&& ConnectorStatus.ACTIVE.equals(device.getConnectorStatus()))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
|
||||
/// 模糊匹配
|
||||
public NettyDevice getUserControl(String userName) {
|
||||
return deviceMap.values().stream()
|
||||
.filter(device -> device.getConnectorId().contains(userName)
|
||||
&& ConnectorStatus.ACTIVE.equals(device.getConnectorStatus()))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据设备ID获取Channel
|
||||
*/
|
||||
public Channel getChannel(String deviceId) {
|
||||
return channelMap.get(deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Channel反查设备ID
|
||||
*/
|
||||
public String getDeviceIdByChannel(Channel channel) {
|
||||
return channelToDeviceIdMap.get(channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据从机查控制的主机
|
||||
*/
|
||||
public NettyDevice getMasterBySlaveId(String slaveId) {
|
||||
return deviceMap.values().stream().filter(x -> Objects.equals(x.getCurrentSlaveId(), slaveId)
|
||||
&& ConnectorStatus.ACTIVE.equals(x.getConnectorStatus())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据从机查所有主机
|
||||
*/
|
||||
public List<NettyDevice> getMastersBySlaveId(String slaveId) {
|
||||
return deviceMap.values().stream().filter(x -> Objects.equals(x.getCurrentSlaveId(), slaveId)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除设备连接(设备下线)
|
||||
*/
|
||||
public void removeDevice(String deviceId) {
|
||||
NettyDevice nettyDevice = deviceMap.remove(deviceId);
|
||||
// Channel channel = channelMap.remove(deviceId);
|
||||
clearChannelToDeviceIdMap(deviceId);
|
||||
// if (channel != null) {
|
||||
// channelToDeviceIdMap.remove(channel);
|
||||
// channel.close();
|
||||
// }
|
||||
if (nettyDevice != null) {
|
||||
// 仅当设备是上位机时,才解绑绑定关系
|
||||
if (nettyDevice.getDeviceType() == ConnectorType.MASTER) {
|
||||
for (Connector connected : new HashSet<>(nettyDevice.getConnectedConnectors())) {
|
||||
nettyDevice.removeConnectedDevice(connected);
|
||||
}
|
||||
} else {
|
||||
// 如果是下位机,则保留绑定关系,只清理channel和状态
|
||||
nettyDevice.setChannel(null);
|
||||
nettyDevice.setConnectorStatus(ConnectorStatus.DISABLED); // 或者设置为离线状态
|
||||
// 注意:此时device仍留在deviceMap中
|
||||
nettyDevice.setLatestStatus(null);
|
||||
deviceMap.put(deviceId, nettyDevice);
|
||||
}
|
||||
System.out.println("Removed device: " + deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断设备是否在线
|
||||
*/
|
||||
public boolean isOnline(String deviceId) {
|
||||
Channel ch = channelMap.get(deviceId);
|
||||
return ch != null && ch.isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断设备是否允许发送消息(ACTIVE状态才允许)
|
||||
*/
|
||||
public boolean canSend(String deviceId) {
|
||||
NettyDevice nettyDevice = deviceMap.get(deviceId);
|
||||
return nettyDevice != null && nettyDevice.canSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断设备是否允许接收消息(非DISABLED状态才允许)
|
||||
*/
|
||||
public boolean canReceive(String deviceId) {
|
||||
NettyDevice nettyDevice = deviceMap.get(deviceId);
|
||||
return nettyDevice != null && nettyDevice.canReceive();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -35,6 +35,8 @@ public class GlobalMemory {
|
||||
|
||||
public static MqttClientUtil mqttClientUtil;
|
||||
|
||||
|
||||
|
||||
public static SiteMemory getSiteMemory(Long orgId, Long siteId) {
|
||||
if (orgId != null && siteId != null) {
|
||||
Map<Long, SiteMemory> siteMemories = orgAttrs.get(orgId);
|
||||
|
||||
@@ -35,7 +35,7 @@ public class SiteMemory {
|
||||
|
||||
public SysSite sysSite;
|
||||
|
||||
public static final ConcurrentHashMap<Long, OrderModel> ordrModelMap = new ConcurrentHashMap<>();
|
||||
public static final ConcurrentHashMap<Long, OrderModel> orderModelMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static final ConcurrentHashMap<String, AlarmOrderConfig> orderConfigMap = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -87,7 +87,19 @@ public class SiteMemory {
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
list.add(errorIdentificationStandard);
|
||||
// 替换已存在的记录,兼容后续保存
|
||||
int index = -1;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (Objects.equals(list.get(i).getErrorCode(), errorIdentificationStandard.getErrorCode())){
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
list.set(index, errorIdentificationStandard);
|
||||
} else {
|
||||
list.add(errorIdentificationStandard);
|
||||
}
|
||||
standards.put(errorIdentificationStandard.getErrorSource(), list);
|
||||
}
|
||||
|
||||
@@ -108,19 +120,19 @@ public class SiteMemory {
|
||||
}
|
||||
|
||||
public void saveOrderModel(OrderModel orderModel) {
|
||||
ordrModelMap.put(orderModel.getId(), orderModel);
|
||||
orderModelMap.put(orderModel.getId(), orderModel);
|
||||
}
|
||||
|
||||
public OrderModel getOrderModel(Long id) {
|
||||
if (id != null) {
|
||||
return ordrModelMap.get(id);
|
||||
return orderModelMap.get(id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeOrderModel(Long id) {
|
||||
if (id != null) {
|
||||
ordrModelMap.remove(id);
|
||||
orderModelMap.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,108 @@
|
||||
package com.maibu.mqtt;
|
||||
|
||||
|
||||
public class MqttTopic {
|
||||
|
||||
public static final String DEVICE_STATUS_TOPIC = "device/%s/realTimeMessage"; // 实时消息
|
||||
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
|
||||
public static final String DEVICE_STATUS_TOPIC = "device/%s/realTimeMessage";
|
||||
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
|
||||
public static final String DEVICE_TASK_STATUS_TOPIC = "task/%s/status";
|
||||
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
|
||||
public static final String DEVICE_TASK_ARRIVE_TOPIC = "task/%s/arrive";
|
||||
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
|
||||
public static final String DEVICE_ERROR_PUSH_TOPIC = "device/%s/error";
|
||||
|
||||
public static final String DEVICE_TASK_STATUS_TOPIC = "task/%s/status"; // 任务状态 完成、取消、暂停、继续
|
||||
/** 产品标识:割草机 */
|
||||
public static final String MOWER_PRODUCT = "mower";
|
||||
|
||||
public static final String DEVICE_TASK_ARRIVE_TOPIC = "task/%s/arrive"; // 任务到达
|
||||
// ==================== 设备连接与状态 ====================
|
||||
/** 设备上线/下线事件上报(设备->平台),对应 TCP 0x03 connect */
|
||||
public static final String MOWER_STATUS_POST = "mower/%s/event/status/post";
|
||||
/** 状态变更确认回复(平台->设备) */
|
||||
// public static final String MOWER_STATUS_REPLY = "mower/%s/event/status/reply";
|
||||
|
||||
public static final String DEVICE_ERROR_PUSH_TOPIC = "device/%s/error"; // 设备错误推送
|
||||
// ==================== 实时运行数据 ====================
|
||||
/** 实时运行数据上报:电压/速度/温度/位置等(设备->平台),对应 TCP 0x02 status */
|
||||
public static final String MOWER_REALTIME_POST = "mower/%s/property/realtime/post";
|
||||
|
||||
// ==================== 心跳保活 ====================
|
||||
/** 设备心跳上报(设备->平台),对应 TCP 0xFF heartbeat */
|
||||
public static final String MOWER_HEARTBEAT_POST = "mower/%s/event/heartbeat/post";
|
||||
/** 心跳确认回复(平台->设备) */
|
||||
public static final String MOWER_HEARTBEAT_REPLY = "mower/%s/event/heartbeat/reply";
|
||||
|
||||
}
|
||||
|
||||
// ==================== 参数配置 ====================
|
||||
/** 下发读取配置请求(平台->设备),对应 TCP 0x05 readConfig */
|
||||
public static final String MOWER_CONFIG_GET = "mower/%s/action/config/get";
|
||||
/** 下发配置修改指令(平台->设备),对应 TCP 0x06 writeConfig */
|
||||
public static final String MOWER_CONFIG_SET = "mower/%s/action/config/set";
|
||||
/** 配置读取/修改响应(设备->平台) */
|
||||
public static final String MOWER_CONFIG_REPLY = "mower/%s/action/config/reply";
|
||||
|
||||
// ==================== 远程控制 ====================
|
||||
/** 远程控制指令下发:前进/后退/转向/割草等(平台->设备),对应 TCP 0x00 remoteControl */
|
||||
public static final String MOWER_CONTROL_SET = "mower/%s/action/control/set";
|
||||
|
||||
// /** 控制指令响应(设备->平台) */
|
||||
// public static final String MOWER_CONTROL_REPLY = "mower/%s/action/control/reply";
|
||||
|
||||
// ==================== 任务管理 ====================
|
||||
/** 路径规划下发(平台->设备),对应 TCP 0x01 path */
|
||||
public static final String MOWER_TASK_ROUTE_SET = "mower/%s/task/route/set";
|
||||
public static final String MOWER_TASK_ROUTE_POST = "mower/%s/task/route/post";
|
||||
/** 启动任务执行(平台->设备),对应 TCP 0x12 interaction */
|
||||
public static final String MOWER_TASK_STATUS_SET = "mower/%s/task/status/set";
|
||||
// /** 暂停任务(平台->设备) */
|
||||
// public static final String MOWER_TASK_PAUSE_SET = "mower/%s/task/pause/set";
|
||||
// /** 继续任务(平台->设备) */
|
||||
// public static final String MOWER_TASK_RESUME_SET = "mower/%s/task/resume/set";
|
||||
// /** 取消任务(平台->设备) */
|
||||
// public static final String MOWER_TASK_CANCEL_SET = "mower/%s/task/cancel/set";
|
||||
// /** 到达路径点上报(设备->平台) */
|
||||
public static final String MOWER_TASK_POINT_POST = "mower/%s/task/point/post";
|
||||
/** 任务状态变更上报:执行中/暂停/完成等(设备->平台) */
|
||||
public static final String MOWER_TASK_STATUS_POST = "mower/%s/task/status/post";
|
||||
/** 任务完成上报(设备->平台) */
|
||||
public static final String MOWER_TASK_FINISH_POST = "mower/%s/task/finish/post";
|
||||
|
||||
// ==================== 错误告警 ====================
|
||||
/** 设备错误告警上报:电机故障/传感器异常等(设备->平台) */
|
||||
public static final String MOWER_ERROR_POST = "mower/%s/event/error/post";
|
||||
|
||||
// ==================== OTA 固件升级 ====================
|
||||
// /** 下发 OTA 升级指令(平台->设备) */
|
||||
// public static final String MOWER_OTA_UPGRADE_SET = "mower/%s/ota/upgrade/set";
|
||||
// /** OTA 升级进度上报(设备->平台) */
|
||||
// public static final String MOWER_OTA_PROGRESS_POST = "mower/%s/ota/progress/post";
|
||||
// /** OTA 升级结果上报(设备->平台) */
|
||||
// public static final String MOWER_OTA_RESULT_POST = "mower/%s/ota/result/post";
|
||||
|
||||
// ==================== 设备信息 ====================
|
||||
// /** 查询设备信息(平台->设备) */
|
||||
// public static final String MOWER_INFO_DEVICE_GET = "mower/%s/info/device/get";
|
||||
// /** 设备信息响应(设备->平台) */
|
||||
// public static final String MOWER_INFO_DEVICE_REPLY = "mower/%s/info/device/reply";
|
||||
/** 设备版本信息上报(设备->平台) */
|
||||
// public static final String MOWER_INFO_VERSION_POST = "mower/%s/info/version/post";
|
||||
|
||||
// ==================== 通配符订阅 ====================
|
||||
/** 订阅所有设备的状态变更事件 */
|
||||
public static final String MOWER_WILDCARD_STATUS = "mower/+/event/status/post";
|
||||
/** 订阅所有设备的实时运行数据 */
|
||||
public static final String MOWER_WILDCARD_REALTIME = "mower/+/property/realtime/post";
|
||||
/** 订阅所有设备的错误告警 */
|
||||
public static final String MOWER_WILDCARD_ERROR = "mower/+/event/error/post";
|
||||
/** 订阅所有设备的任务状态变更 */
|
||||
public static final String MOWER_WILDCARD_TASK_STATUS = "mower/+/task/status/post";
|
||||
|
||||
/**
|
||||
* 格式化 Topic 模板,将 %s 替换为设备ID
|
||||
*
|
||||
* @param template Topic 模板,如 {@link #MOWER_REALTIME_POST}
|
||||
* @param deviceId 设备ID
|
||||
* @return 格式化后的完整 Topic
|
||||
*/
|
||||
public static String formatTopic(String template, String deviceId) {
|
||||
return String.format(template, deviceId);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JSON 工具类
|
||||
@@ -87,10 +88,11 @@ public class JsonUtils {
|
||||
|
||||
/**
|
||||
* 将字符串解析成指定类型的对象
|
||||
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
|
||||
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use =
|
||||
* JsonTypeInfo.Id.CLASS) 的场景下,
|
||||
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
|
||||
*
|
||||
* @param text 字符串
|
||||
* @param text 字符串
|
||||
* @param clazz 类型
|
||||
* @return 对象
|
||||
*/
|
||||
@@ -127,7 +129,8 @@ public class JsonUtils {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
return objectMapper.readValue(text,
|
||||
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
|
||||
Reference in New Issue
Block a user