21 Commits

Author SHA1 Message Date
64f54c4191 #地图相关推送修改 2026-09-21 10:25:49 +08:00
4f7f32eeb2 #update 地图统一推送功能 2026-09-14 10:37:27 +08:00
cfd2906b71 #位置信息增加当前设备任务执行情况 2026-09-11 09:29:06 +08:00
985f501e76 #更新计算累计路程相关 2026-09-10 14:35:02 +08:00
a6f662b15b #老板的上位机登录 2026-09-09 13:00:31 +08:00
4ee3f6be90 #优化更新 连接上位机相关 2026-09-09 09:01:54 +08:00
fda1060b38 # update 更新导入 设备属性的功能 2026-09-03 10:32:57 +08:00
5f369a159d #更新优化 2026-09-02 05:03:10 +00:00
a92ec91cd2 #优化路径规划下发 任务状态接收相关 2026-08-28 06:32:11 +00:00
9ff685e0c1 # 测试调整路径规划消息下发处理 2026-08-25 08:36:45 +00:00
544efbd0f9 # update 权限相关 2026-08-25 12:36:10 +08:00
33df2ff0f7 # update 权限相关 2026-08-25 09:19:12 +08:00
27f4796c55 # update 2026-08-21 09:20:08 +08:00
16ec98e36a # 上位机心跳 以及定位信息发送部分 2026-08-19 16:57:55 +08:00
2f94639f0a Merge branch 'dev_control' into dev_new_connect
# Conflicts:
#	.idea/workspace.xml
2026-08-19 11:05:09 +08:00
3eb7f7bc31 #update 2026-08-19 11:03:00 +08:00
1b19e32aa5 #update 2026-08-19 11:02:52 +08:00
80bfdd134d #update mqtt 上位机 2026-08-17 11:49:33 +08:00
ffd9720939 #update mqtt 上位机 2026-08-07 10:22:03 +08:00
5f3929ae16 #update mqtt 上位机 2026-08-07 09:46:55 +08:00
3ba32b19ce #update mqtt 上位机 2026-08-07 08:45:43 +08:00
85 changed files with 2710 additions and 817 deletions

105
.idea/workspace.xml generated
View File

@@ -6,21 +6,14 @@
<component name="ChangeListManager">
<list default="true" id="84ac1b15-3cfa-4375-9957-45b7b581d04e" name="Changes" comment="# commit">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-admin/src/main/resources/application-dev.yml" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-admin/src/main/resources/application-dev.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-common/src/main/java/com/maibu/core/business/Device.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-common/src/main/java/com/maibu/core/business/Device.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-external/src/main/java/com/maibu/uav/controller/UAVController.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-external/src/main/java/com/maibu/uav/controller/UAVController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDetailDTO.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDetailDTO.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-external/src/main/java/com/maibu/uav/service/UAVService.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-external/src/main/java/com/maibu/uav/service/UAVService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-framework/src/main/java/com/maibu/config/SecurityConfig.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-framework/src/main/java/com/maibu/config/SecurityConfig.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/NettyServer.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/NettyServer.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-common/src/main/java/com/maibu/core/business/device/NettyDevice.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-common/src/main/java/com/maibu/core/business/device/NettyDevice.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-common/src/main/java/com/maibu/memory/DeviceSessionManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-common/src/main/java/com/maibu/memory/DeviceSessionManager.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/dto/DeviceBindDTO.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/dto/DeviceBindDTO.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/CommandForwardHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/CommandForwardHandler.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/DeviceConnectHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/DeviceConnectHandler.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/HeaderFooterDecoder.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/HeaderFooterDecoder.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/HexEncoder.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/netty/handler/HexEncoder.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/java/com/maibu/controller/DeviceController.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/java/com/maibu/controller/DeviceController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/java/com/maibu/service/IDeviceService.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/java/com/maibu/service/IDeviceService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/java/com/maibu/service/impl/DeviceServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/java/com/maibu/service/impl/DeviceServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/resources/mapper/iot/DeviceMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/resources/mapper/iot/DeviceMapper.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-web-middleware/src/main/java/com/maibu/common/MiddleConstant.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-web-middleware/src/main/java/com/maibu/common/MiddleConstant.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/service/TransferDeviceService.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-netty-server/src/main/java/com/maibu/service/TransferDeviceService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-web-middleware/src/main/java/com/maibu/dto/NettySwitchDeviceDTO.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-web-middleware/src/main/java/com/maibu/dto/NettySwitchDeviceDTO.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/maibu-web-middleware/src/main/java/com/maibu/netty/handler/ClientHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/maibu-web-middleware/src/main/java/com/maibu/netty/handler/ClientHandler.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -40,6 +33,11 @@
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="main" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="HttpClientEndpointsTabState">
@@ -79,40 +77,40 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;Application.test.executor&quot;: &quot;Debug&quot;,
&quot;HTTP Request.MiddlePlatformApplication | #1.executor&quot;: &quot;Run&quot;,
&quot;HTTP Request.MiddlePlatformApplication | #2.executor&quot;: &quot;Run&quot;,
&quot;HTTP Request.MiddlePlatformApplication | #3.executor&quot;: &quot;Run&quot;,
&quot;Maven.MiddlePlatform [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.MiddlePlatform [install].executor&quot;: &quot;Run&quot;,
&quot;Maven.maibu-netty-server [clean].executor&quot;: &quot;Run&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RequestMappingsPanelOrder0&quot;: &quot;0&quot;,
&quot;RequestMappingsPanelOrder1&quot;: &quot;1&quot;,
&quot;RequestMappingsPanelWidth0&quot;: &quot;75&quot;,
&quot;RequestMappingsPanelWidth1&quot;: &quot;75&quot;,
&quot;RunOnceActivity.MCP Project settings loaded&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
&quot;Spring Boot.MiddlePlatformApplication.executor&quot;: &quot;Debug&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/maibu/Test/MiddlePlatform/maibu-external/src/main/java/com/maibu/uav&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;onboarding.tips.debug.path&quot;: &quot;D:/code/MiddlePlatform/maibu-service/maibu-system-service/src/main/java/com/maibu/Main.java&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;project.propVCSSupport.CommitDialog&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
"Application.test.executor": "Debug",
"HTTP Request.MiddlePlatformApplication | #1.executor": "Run",
"HTTP Request.MiddlePlatformApplication | #2.executor": "Run",
"HTTP Request.MiddlePlatformApplication | #3.executor": "Run",
"Maven.MiddlePlatform [clean].executor": "Run",
"Maven.MiddlePlatform [install].executor": "Run",
"Maven.maibu-netty-server [clean].executor": "Run",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RequestMappingsPanelOrder0": "0",
"RequestMappingsPanelOrder1": "1",
"RequestMappingsPanelWidth0": "75",
"RequestMappingsPanelWidth1": "75",
"RunOnceActivity.MCP Project settings loaded": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
"Spring Boot.MiddlePlatformApplication.executor": "Debug",
"git-widget-placeholder": "dev__control",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "/home/maibu/Test/MiddlePlatform/maibu-external/src/main/java/com/maibu/uav",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"onboarding.tips.debug.path": "D:/code/MiddlePlatform/maibu-service/maibu-system-service/src/main/java/com/maibu/Main.java",
"settings.editor.selected.configurable": "project.propVCSSupport.CommitDialog",
"vue.rearranger.settings.migration": "true"
}
}</component>
}]]></component>
<component name="RdControllerToolWindowsLayoutState" isNewUi="true">
<layout>
<window_info id="Bookmarks" side_tool="true" />
@@ -190,17 +188,6 @@
<configuration name="MiddlePlatformApplication | #3" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" temporary="true" nameIsGenerated="true" path="$APPLICATION_CONFIG_DIR$/scratches/MiddlePlatformApplication.http" executionIdentifier="#3" index="3" runType="Run single request">
<method v="2" />
</configuration>
<configuration default="true" type="JetRunConfigurationType">
<module name="maibu-iot-service" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
<module name="maibu-iot-service" />
<option name="filePath" />
<method v="2" />
</configuration>
<configuration name="MiddlePlatformApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" temporary="true" nameIsGenerated="true">
<option name="FRAME_DEACTIVATION_UPDATE_POLICY" value="UpdateClassesAndResources" />
<module name="maibu-admin" />
@@ -725,7 +712,7 @@
<line-breakpoint enabled="true" type="java-method">
<url>file://$PROJECT_DIR$/maibu-service/maibu-iot-service/src/main/java/com/maibu/controller/IOTWorkOrderController.java</url>
<line>58</line>
<properties class="com.maibu.controller.IOTWorkOrderController" method="edit">
<properties class="com.maibu.controller.IOTWorkOrderController" method="list">
<option name="EMULATED" value="true" />
<option name="WATCH_EXIT" value="false" />
</properties>

View File

@@ -3,7 +3,7 @@ spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/maibu?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://1.95.137.212:3306/maibu?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: satabot
password: satabot
@@ -31,7 +31,7 @@ spring:
mqtt:
username: maibu # 账号
password: jsmbzn520 # 密码
host-url: tcp://localhost:1883 # mqtt连接tcp地址
host-url: tcp://1.95.137.212:1883 # mqtt连接tcp地址
client-id: maibu-mqtt-client # 客户端Id,不能相同,采用随机数 ${random.value}
default-topic: test # 默认主题
timeout: 30 # 超时时间

View File

@@ -97,6 +97,16 @@ public class DeviceTask {
return builder(FastBeeConstant.TASK.DEVICE_ERROR_MONITOR);
}
@Bean(FastBeeConstant.TASK.DEVICE_HEARTBEAT_MONITOR)
public Executor deviceHeartBeatMonitor() {
return builder(FastBeeConstant.TASK.DEVICE_HEARTBEAT_MONITOR);
}
@Bean(FastBeeConstant.TASK.MAP_MESSAGE_PUSH)
public Executor mapMessagePush() {
return builder(FastBeeConstant.TASK.MAP_MESSAGE_PUSH);
}
@Bean(FastBeeConstant.TASK.DEVICE_TASK_HANDLER)
public Executor deviceTaskHandler() {
return builder(FastBeeConstant.TASK.DEVICE_TASK_HANDLER);

View File

@@ -21,7 +21,11 @@ public class Constant {
public static final AttributeKey<String> ATT_SITE_ID = AttributeKey.valueOf("siteId");
public static final String WheelGainCoefficients = "WheelGainCoefficients";
public static final String WheelGainCoefficients = "WheelGainCoefficients";
public static final String mqttClientPrefix = "common-device-client-";
public static final String mqttClientPrefix = "common-device-client-";
public static final String mqttHandleKey = "hostHandle";
public static final Long hostOfflineInterval = 10000L; //单位毫秒
}

View File

@@ -100,6 +100,18 @@ public interface FastBeeConstant {
* 设备错误
*/
String DEVICE_ERROR_MONITOR = "deviceErrorMonitor";
/**
* 设备错误
*/
String DEVICE_HEARTBEAT_MONITOR = "deviceHeartBeatMonitor";
/**
* 设备错误
*/
String MAP_MESSAGE_PUSH = "mapMessagePush";
/**
* 设备任务处理线程
*/

View File

@@ -1,9 +1,9 @@
package com.maibu.common;
import lombok.Data;
package com.maibu.constant;
public class NettyCacheKey {
public static final String deviceRunningStatusKey = "running_status:";
public static final String latestPositonKey = "latest_position:";
}

View File

@@ -13,6 +13,8 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.maibu.annotation.Excel;
import com.maibu.core.domain.BaseDO;
import com.maibu.core.host.LocationMessage;
import com.maibu.core.host.VehicleMessage;
import com.maibu.influxdb.MowerRealTimeData;
import io.swagger.annotations.ApiModel;
@@ -72,7 +74,6 @@ public class Device extends BaseDO {
@Excel(name = "产品名称")
private String productName;
/**
* 租户名称
*/
@@ -87,7 +88,6 @@ public class Device extends BaseDO {
@Excel(name = "固件版本")
private BigDecimal firmwareVersion;
/**
* WIFI固件版本
*/
@@ -95,7 +95,6 @@ public class Device extends BaseDO {
@Excel(name = "HTTP推送固件版本")
private BigDecimal wirelessVersion;
/**
* 设备状态(1-未激活,2-禁用,3-在线,4-离线)
*/
@@ -106,12 +105,12 @@ public class Device extends BaseDO {
/**
* 设备在线状态(1-在线,2-离线)
*/
@ApiModelProperty("设备状态(1-在线,2-离线)")
@ApiModelProperty("设备状态(1-在线,0-离线)")
@Excel(name = "设备在线状态")
private Integer onlineStatus;
@TableField(exist = false)
private Integer feStatus; //前端大屏显示 1任务中/2空闲
private Integer feStatus; // 前端大屏显示 1任务中/2空闲
/**
* wifi信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85])
@@ -194,7 +193,6 @@ public class Device extends BaseDO {
@ApiModelProperty("设备摘要")
private String summary;
/**
* 是否是模拟设备
*/
@@ -206,12 +204,12 @@ public class Device extends BaseDO {
@ApiModelProperty("子设备地址")
private Integer slaveId;
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long siteId;
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long orgId;
/**
* 租户ID
*/
@@ -220,7 +218,6 @@ public class Device extends BaseDO {
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long tenantId;
@TableField(exist = false)
private DeviceRunningStatusHistory lastRunningStatus;
@@ -228,12 +225,19 @@ public class Device extends BaseDO {
private MowerRealTimeData lastMowerRealTimeData;
@TableField(exist = false)
private Map<Integer,Integer> statusStatistics;
private Map<Integer, Integer> statusStatistics;
@TableField(exist = false)
private DeviceRunParam runParam;
private Boolean hasRegistered;
private boolean hasRegistered = false;
private Boolean hasHost;
@TableField(exist = false)
private LocationMessage locationMessage;
@TableField(exist = false)
private VehicleMessage vehicleMessage;
}

View File

@@ -1,18 +1,14 @@
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.fasterxml.jackson.annotation.JsonInclude;
import com.maibu.core.domain.BaseDO;
import com.maibu.core.enums.ConnectType;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)

View File

@@ -1,17 +1,17 @@
package com.maibu.core.business;
import java.time.LocalDateTime;
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.core.domain.BaseDO;
import com.maibu.core.domain.OrgBaseDO;
import com.maibu.core.enums.DeviceTaskStaus;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("iot_device_task")
@@ -39,4 +39,7 @@ public class DevicePlanTask extends OrgBaseDO {
private LocalDateTime canExecuteTime; // 这个时间之前都不能执行
@TableField(exist = false)
private volatile boolean taskAccepted = false; //任务被上位机接收
}

View File

@@ -4,6 +4,8 @@ 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.maibu.annotation.Excel;
import com.maibu.constant.CommandConstant;
import com.maibu.core.domain.BaseDO;
@@ -13,6 +15,7 @@ import lombok.EqualsAndHashCode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@@ -22,7 +25,7 @@ public class DeviceRunParam extends BaseDO {
@TableId(type = IdType.AUTO)
private Long id;
private String deviceId;
// private String deviceId;
private Long siteId;
@@ -30,6 +33,40 @@ public class DeviceRunParam extends BaseDO {
private Integer runSpeed; //不下发到设备自己用
@Excel(name = "设备序列号", cellType = Excel.ColumnType.STRING, prompt = "设备序列号")
private String deviceId;//设备序列号
@Excel(name = "产品型号", cellType = Excel.ColumnType.STRING, prompt = "产品型号")
private String productModel;//产品型号
@Excel(name = "设备类型", cellType = Excel.ColumnType.STRING, prompt = "设备类型")
private String deviceType;//设备类型:mower-割草机器人
@Excel(name = "RTK模式", cellType = Excel.ColumnType.STRING, prompt = "RTK模式")
private String rtkMode;//RTK模式:network-4G网络差分
@Excel(name = "摄像头数量", cellType = Excel.ColumnType.STRING, prompt = "摄像头数量")
private Integer cameraNum;//摄像头数量
@Excel(name = "动力类型", cellType = Excel.ColumnType.STRING, prompt = "动力类型")
private String powerType;//动力类型:hybrid-混动
@Excel(name = "车轮类型", cellType = Excel.ColumnType.STRING, prompt = "车轮类型")
private String wheelType;//车轮类型:track-履带式
@Excel(name = "发动机类型", cellType = Excel.ColumnType.STRING, prompt = "发动机类型")
private String engineType;//发动机类型:honda-本田发动机
@Excel(name = "是否搭载上位机",cellType = Excel.ColumnType.NUMERIC)
private Integer hasHost = 1;//是否搭载上位机
@Excel(name = "设备能力列表")
@TableField(exist = false)
private String capabilities;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> capabilityList;//设备能力列表:heartbeat-上报心跳/location-上报位置/vehicle_status-上报整机状态/task_control-任务控制/single_point-单点作业/coverage-全覆盖割草作业/path_upload-作业路径上传
@TableField(exist = false)
public byte header1;
@@ -117,112 +154,6 @@ public class DeviceRunParam extends BaseDO {
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) {

View File

@@ -20,6 +20,8 @@ public class WorkRecord extends OrgBaseDO {
private String workName;
private Long userId;
private String mode;//导航任务模式 single_point|coverage
// private String jsonData;
@TableField(typeHandler = JacksonTypeHandler.class)

View File

@@ -1,35 +1,39 @@
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;
import com.maibu.core.business.Device;
import com.maibu.core.business.DevicePlanTask;
import com.maibu.core.business.DeviceRunParam;
import com.maibu.core.business.DeviceRunStatistics;
import com.maibu.core.business.PlanPath;
import com.maibu.core.business.WorkRecord;
import com.maibu.core.business.*;
import com.maibu.core.business.path.LatAndLngEntity;
import com.maibu.core.business.path.RoutePlanSendEntity;
import com.maibu.core.enums.ConnectorType;
import com.maibu.core.enums.DeviceTaskStaus;
import com.maibu.core.host.HostWorkRecordCreateDTO;
import com.maibu.core.host.MutiPointEntity;
import com.maibu.core.host.PointEntity;
import com.maibu.mapper.DevicePlanTaskMapper;
import com.maibu.mapper.WorkRecordMapper;
import com.maibu.memory.GlobalMemory;
import com.maibu.memory.SiteMemory;
import com.maibu.mqtt.MqttTopic;
import com.maibu.utils.StringUtils;
import com.maibu.utils.json.JsonUtils;
import com.maibu.utils.spring.SpringUtils;
import io.netty.buffer.Unpooled;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@EqualsAndHashCode(callSuper = true)
@@ -58,8 +62,8 @@ public class NettyDevice extends Connector {
// todo 注册的时候更新 siteId
private Device device;
//todo 假如设置一个当前控制的token
private String currentControlToken;
// todo 当前登录的账号的token 以token为控制的权限
private String token;
@JsonIgnore
private DevicePlanTaskMapper devicePlanTaskMapper = SpringUtils.getBean(DevicePlanTaskMapper.class);
@@ -67,6 +71,11 @@ public class NettyDevice extends Connector {
@JsonIgnore
private WorkRecordMapper workRecordMapper = SpringUtils.getBean(WorkRecordMapper.class);
@JsonIgnore
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
private static final String mqttClientId = "maibu-mqtt-client";
public NettyDevice(String connectorId) {
super(connectorId);
this.deviceType = ConnectorType.MASTER; // 默认上位机角色
@@ -77,6 +86,10 @@ public class NettyDevice extends Connector {
this.deviceType = deviceType;
}
public void status(Integer onlineStatus) {
this.onlineStatus = onlineStatus;
}
// todo 如何确定命令已收到?
public void sendNextPoint() {
if (task.getTaskStaus().equals(DeviceTaskStaus.PAUSE))
@@ -109,13 +122,14 @@ public class NettyDevice extends Connector {
}
public synchronized Long finishTask() {
sendPathCommand((byte) 0x01, (short) 0, 0.00d, 0.00d, (short) 0);
// sendPathCommand((byte) 0x01, (short) 0, 0.00d, 0.00d, (short) 0);
long finishId = task.getId();
task.setTaskStaus(DeviceTaskStaus.FINISH);
task.setFinishTime(LocalDateTime.now());
devicePlanTaskMapper.saveOrUpdate(task);
SiteMemory siteMemory = GlobalMemory.getSiteMemory(task.getOrgId(), task.getSiteId());
siteMemory.removeDevicePlanExecute(task.getDeviceId());
GlobalMemory.taskPathMap.remove(task.getId());
task = null;
currentTaskId = null;
currentPoint = null;
@@ -124,15 +138,59 @@ public class NettyDevice extends Connector {
return finishId;
}
public synchronized boolean cancelTask() {
public synchronized boolean cancelingTask() throws MqttException {
if (task != null) {
WorkRecord workRecord = workRecordMapper.selectById(task.getRouteId());
if (workRecord != null) {
HostWorkRecordCreateDTO dto = new HostWorkRecordCreateDTO();
dto.setAction("cancel");
dto.setCommand_id(task.getId() + "_" + System.currentTimeMillis());
dto.setTask_id(String.valueOf(task.getId()));
dto.setSn(task.getDeviceId());
dto.setMode(workRecord.getMode());
dto.setTimestamp(System.currentTimeMillis());
dto.setMessage_id(task.getId() + "_" + System.currentTimeMillis());
dto.setProtocol_version("1.4");
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
String.format(MqttTopic.MOWER_TASK_ROUTE_SET, task.getDeviceId()),
JsonUtils.toJsonString(dto));
task.setTaskStaus(DeviceTaskStaus.CANCELING);
task.setUpdateTime(LocalDateTime.now());
devicePlanTaskMapper.saveOrUpdate(task);
}
}
return true;
}
public synchronized boolean canceledTask() {
if (task != null) {
sendPathCommand((byte) 0x01, (short) 0, 0.00d, 0.00d, (short) 0);
task.setTaskStaus(DeviceTaskStaus.CANCELED);
task.setUpdateTime(LocalDateTime.now());
devicePlanTaskMapper.saveOrUpdate(task);
SiteMemory siteMemory = GlobalMemory.getSiteMemory(task.getOrgId(), task.getSiteId());
siteMemory.removeDevicePlanPrepare(task);
siteMemory.removeDevicePlanExecute(task.getDeviceId());
GlobalMemory.taskPathMap.remove(task.getId());
task = null;
currentTaskId = null;
currentPoint = null;
locationQueue.clear();
executingTask = false;
}
return true;
}
public synchronized boolean failedTask() {
if (task != null) {
task.setTaskStaus(DeviceTaskStaus.FAILED);
task.setUpdateTime(LocalDateTime.now());
devicePlanTaskMapper.saveOrUpdate(task);
SiteMemory siteMemory = GlobalMemory.getSiteMemory(task.getOrgId(), task.getSiteId());
siteMemory.removeDevicePlanPrepare(task);
siteMemory.removeDevicePlanExecute(task.getDeviceId());
GlobalMemory.taskPathMap.remove(task.getId());
task = null;
currentTaskId = null;
currentPoint = null;
@@ -156,8 +214,9 @@ public class NettyDevice extends Connector {
}
}
//配置左右轮增益系数
public void sendWheelGainCoefficientsCommand(double leftForwardGain, double leftBackwardGain, double rightForwardGain, double rightBackwardGain) {
// 配置左右轮增益系数
public void sendWheelGainCoefficientsCommand(double leftForwardGain, double leftBackwardGain,
double rightForwardGain, double rightBackwardGain) {
ByteBuffer buffer = ByteBuffer.allocate(39);
buffer.order(ByteOrder.LITTLE_ENDIAN);
@@ -189,7 +248,6 @@ public class NettyDevice extends Connector {
}
}
public void readConfigCommand() {
byte[] bytes = DeviceRunParam.toReadBytes();
log.debug("下发读取配置的报文:{}", toHexString(bytes));
@@ -238,27 +296,110 @@ public class NettyDevice extends Connector {
}
}
public void startTask() {
try {
if (executingTask)
return;
if (task != null) {
Long routeId = task.getRouteId();
WorkRecord workRecord = workRecordMapper.selectById(routeId);
if (workRecord != null) {
PlanPath planPath = workRecord.getJsonData();
if (planPath != null && !CollectionUtils.isEmpty(planPath.getPath())) {
planPath.getPath().forEach(x -> {
locationQueue.offer(x);
});
}
}
task.setStartTime(LocalDateTime.now());
}
executingTask = true;
sendNextPoint();
if (task != null) {
// startTime 在启动前记录
task.setStartTime(LocalDateTime.now());
AtomicInteger count = new AtomicInteger(0);
ScheduledFuture<?>[] futureHolder = new ScheduledFuture<?>[1];
futureHolder[0] = executor.scheduleWithFixedDelay(() -> {
int current = count.incrementAndGet();
try {
if (task.isTaskAccepted()) {
// 成功,停止后续执行
futureHolder[0].cancel(false);
log.info("任务执行成功,停止重试,执行次数={}", current);
return;
}
sendNavigationCommand();
// 达到最大次数
if (current >= 10) {
futureHolder[0].cancel(false);
log.warn("任务执行达到最大次数,停止执行");
executingTask = false;
task.setTaskStaus(DeviceTaskStaus.FAILED);
devicePlanTaskMapper.saveOrUpdate(task);
task = null;
}
} catch (Exception e) {
log.error("任务执行异常,次数={}", current, e);
// 异常是否算一次,可以根据你的业务决定
if (current >= 10) {
futureHolder[0].cancel(false);
}
executingTask = false;
task.setTaskStaus(DeviceTaskStaus.FAILED);
devicePlanTaskMapper.saveOrUpdate(task);
task = null;
}
}, 0, 5, TimeUnit.SECONDS);
}
} catch (Exception e) {
throw new RuntimeException(e);
log.error("发送开始任务:error {} ", e.getMessage());
}
}
public void sendNavigationCommand() throws MqttException {
log.debug("发送开始任务 taskStatus:{},routeId:{}", task.getTaskStaus(), task.getRouteId());
if (!task.getTaskStaus().equals(DeviceTaskStaus.EXECUTING))
return;
WorkRecord workRecord = workRecordMapper.selectById(task.getRouteId());
log.debug("发送开始任务 workRecord {}", workRecord);
if (workRecord != null) {
HostWorkRecordCreateDTO dto = new HostWorkRecordCreateDTO();
dto.setAction("start");
dto.setCommand_id(task.getId() + "_" + System.currentTimeMillis());
dto.setTask_id(String.valueOf(task.getId()));
dto.setSn(task.getDeviceId());
dto.setMode(workRecord.getMode());
dto.setTimestamp(System.currentTimeMillis());
dto.setMessage_id(task.getId() + "_" + System.currentTimeMillis());
dto.setProtocol_version("1.4");
PointEntity point = new PointEntity();
if (!StringUtils.isEmpty(workRecord.getMode())) {
if (workRecord.getMode().equals("single_point")) {
point.setLongitude(workRecord.getJsonData().getPath().get(0).getLng());
point.setLatitude(workRecord.getJsonData().getPath().get(0).getLat());
}
if (workRecord.getMode().equals("coverage")) {
List<MutiPointEntity> boundary_points = new ArrayList<>();
// workRecord.getJsonData().getPath().forEach(x -> {
// MutiPointEntity mutiPoint = new MutiPointEntity();
// mutiPoint.setLongitude(x.getLng());
// mutiPoint.setLatitude(x.getLat());
// mutiPoint.setIndex();
// boundary_points.add(mutiPoint);
// });
List<LatAndLngEntity> path = workRecord.getJsonData().getPath();
for (int i = 0; i < path.size(); i++) {
LatAndLngEntity x = path.get(i);
MutiPointEntity mutiPoint = new MutiPointEntity();
mutiPoint.setLongitude(x.getLng());
mutiPoint.setLatitude(x.getLat());
mutiPoint.setIndex(i);
boundary_points.add(mutiPoint);
}
point.setBoundary_points(boundary_points);
}
}
dto.setData(point);
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
String.format(MqttTopic.MOWER_TASK_ROUTE_SET, task.getDeviceId()),
JsonUtils.toJsonString(dto));
}
}

View File

@@ -0,0 +1,18 @@
package com.maibu.core.business.dto;
import lombok.Data;
@Data
public class DeviceMapPushDTO {
private String id;
private double lat;
private double lng;
private double y; //yaw
private String f; //fix_status
private double a; //altitude
private String s;//status 运行状态
private Long t; //taskId 任务id
private double b; //电量
private boolean ha = false;//hasAlarm 有无告警
}

View File

@@ -20,4 +20,8 @@ public class DeviceTaskCommandDTO {
private Long taskId;
private Long regionId;
private boolean forceCancel = false; //强制取消 不等回复
}

View File

@@ -1,4 +1,4 @@
package com.maibu.dto;
package com.maibu.core.business.dto;
import com.maibu.core.business.path.LatAndLngEntity;
import com.maibu.core.enums.MesType;

View File

@@ -0,0 +1,12 @@
package com.maibu.core.business.dto;
import com.maibu.core.enums.MesType;
import lombok.Data;
import java.util.List;
@Data
public class WebMapMessageDTO {
private List<DeviceMapPushDTO> data;
private MesType type;
}

View File

@@ -0,0 +1,16 @@
package com.maibu.core.business.dto;
import java.util.List;
import com.maibu.core.business.path.LatAndLngEntity;
import com.maibu.core.enums.MesType;
import lombok.Data;
@Data
public class WebRouteStatusMessageDTO {
private List<LatAndLngEntity> data;
private String deviceId;
private Long taskId;
private MesType type;
}

View File

@@ -1,4 +1,4 @@
package com.maibu.dto;
package com.maibu.core.business.dto;
import lombok.Data;

View File

@@ -1,7 +0,0 @@
package com.maibu.core.business.inter;
public interface DeviceWebSocketInterace {
void sendWebsoketMes(String key, String data);
}

View File

@@ -0,0 +1,10 @@
package com.maibu.core.business.inter;
public interface DeviceWebSocketInterface {
void sendWebsocketMes(String key, String data);
void sendMapWebsocketMes(Long siteId,String data);
}

View File

@@ -14,10 +14,19 @@ public class WebsocketMesDispather {
private ApplicationContext applicationContext;
public void dispather(String key, String data) {
Map<String, DeviceWebSocketInterace> beanMap = applicationContext.getBeansOfType(DeviceWebSocketInterace.class);
Map<String, DeviceWebSocketInterface> beanMap = applicationContext.getBeansOfType(DeviceWebSocketInterface.class);
// 遍历
beanMap.values().forEach(x -> {
x.sendWebsoketMes(key, data);
x.sendWebsocketMes(key, data);
});
}
public void mapMessageDispather(Long siteId, String data) {
Map<String, DeviceWebSocketInterface> beanMap = applicationContext.getBeansOfType(DeviceWebSocketInterface.class);
// 遍历
beanMap.values().forEach(x -> {
x.sendMapWebsocketMes(siteId, data);
});
}

View File

@@ -1,14 +1,15 @@
package com.maibu.core.domain.entity;
import java.util.List;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.maibu.core.domain.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName(value = "sys_region")
@@ -29,4 +30,6 @@ public class SysRegion extends BaseDO {
private Integer status;
private Long bindRoute;// 绑定的线路id
}

View File

@@ -43,4 +43,6 @@ public class SysSite extends BaseDO {
private boolean dingTalkOrderPush = false;// 工单是否推送
private String siteMapName; // geoserver 地图名称
}

View File

@@ -48,6 +48,12 @@ public class LoginUser implements UserDetails {
*/
private String token;
/**
* 用户唯一标识 用户控制设备
*/
private String realToken;
/**
* 登录时间
*/
@@ -108,7 +114,7 @@ public class LoginUser implements UserDetails {
this.permissions = permissions;
}
public LoginUser(Long userId, Long deptId, Long orgId, Long siteId, Long roleId,String language, SysUser user, Set<String> permissions) {
public LoginUser(Long userId, Long deptId, Long orgId, Long siteId, Long roleId, String language, SysUser user, Set<String> permissions) {
this.userId = userId;
this.deptId = deptId;
this.orgId = orgId;

View File

@@ -7,12 +7,13 @@ import lombok.Getter;
@AllArgsConstructor
public enum DeviceTaskStaus {
NEW("NEW","新建"),
EXECUTING("EXECUTING","执行中"),
PAUSE("PAUSE","暂停中"),
CANCELED("CANCELED","已取消"),
FINISH("FINISH","执行成功"),
FAILED("FAILED","执行失败");
NEW("NEW", "新建"),
EXECUTING("EXECUTING", "执行中"),
PAUSE("PAUSE", "暂停中"),
CANCELED("CANCELED", "已取消"),
CANCELING("CANCELING", "取消中"),
FINISH("FINISH", "执行成功"),
FAILED("FAILED", "执行失败");
private final String code;

View File

@@ -3,6 +3,7 @@ package com.maibu.core.enums;
public enum MesType {
AUTH, // 登录连接权限web->be
deviceInfo, // 设备状态 be->web
locationInfo,
path, // 路径下发 web->be
have_logged_in, // 消息
device_status_changed, // 上下线状态变化
@@ -13,5 +14,7 @@ public enum MesType {
remoteControl,
detectionReport, // 障碍物消息
switchPermission, // 切换控制申请
switchResult;// 切换结果
switchResult,// 切换结果
route_post,// 路径规划 点位推送
mapPush;
}

View File

@@ -0,0 +1,12 @@
package com.maibu.core.host;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class HeartBeatDTO extends HostBase {
private String status;
private Long sysTimestamp; //服务器的时间
}

View File

@@ -0,0 +1,13 @@
package com.maibu.core.host;
import lombok.Data;
@Data
public class HostBase {
private String protocol_version;
private String message_id;
private String sn;
private Long timestamp; //上位机的时间
}

View File

@@ -0,0 +1,13 @@
package com.maibu.core.host;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class HostLocationRelTimeDTO extends HostBase {
private LocationMessage data;
}

View File

@@ -0,0 +1,18 @@
package com.maibu.core.host;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class HostNavigationReplyDTO extends HostBase {
private String task_id; //任务唯一编号
private String command_id;//指令唯一编号
private boolean accepted;//是否接收
private String code;//任务动作 OK|INVALID|BUSY|ERROR
private String message;
}

View File

@@ -0,0 +1,19 @@
package com.maibu.core.host;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class HostTaskStatusDTO extends HostBase{
private String task_id; //任务唯一编号
private String command_id;//指令唯一编号
private String mode;//导航任务模式 single_point|coverage
private String status;//任务动作 accepted|running|paused|completed|cancelled|failed
private HostTaskStatusDetailDTO data;
}

View File

@@ -0,0 +1,12 @@
package com.maibu.core.host;
import lombok.Data;
@Data
public class HostTaskStatusDetailDTO {
private float progress;
private String error_code;
private String error_message;
}

View File

@@ -0,0 +1,13 @@
package com.maibu.core.host;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class HostVehicleRelTimeDTO extends HostBase {
private VehicleMessage data;
}

View File

@@ -0,0 +1,20 @@
package com.maibu.core.host;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class HostWorkRecordCreateDTO extends HostBase {
private String task_id; //任务唯一编号
private String command_id;//指令唯一编号
private String mode;//导航任务模式 single_point|coverage
private String action;//任务动作 start/pause/resume/cancel
// private String frame_id; //WGS84
private PointEntity data;
}

View File

@@ -0,0 +1,27 @@
package com.maibu.core.host;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.maibu.core.domain.OrgNoUserDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("iot_host_location_message")
public class LocationMessage extends OrgNoUserDO {
@TableId(type = IdType.AUTO)
private Long id;
private String deviceId;
private double latitude;
private double longitude;
private double yaw;// 目标航向,正北0°、顺时针正、0-360°
private String fix_status;
private double altitude;
}

View File

@@ -0,0 +1,14 @@
package com.maibu.core.host;
import lombok.Data;
@Data
public class MutiPointEntity {
private int index;
private double latitude;
private double longitude;
private double yaw;//目标航向,正北0°、顺时针正、0-360°
}

View File

@@ -0,0 +1,20 @@
package com.maibu.core.host;
import lombok.Data;
import java.util.List;
@Data
public class PointEntity {
private double latitude;
private double longitude;
private double yaw;//目标航向,正北0°、顺时针正、0-360°
private List<MutiPointEntity> boundary_points;
private double cell_size;//覆盖分辨率,单位m
private double angle;//覆盖作业方向,单位度
}

View File

@@ -0,0 +1,16 @@
package com.maibu.core.host;
import lombok.Data;
@Data
public class VehicleMessage {
private double speed;
private double battery_percentage;// 电量
private double battery_voltage;// 电压
private String motion;// moving|stopped|blocked
private boolean emergency_stop; // 急停状态
private double distance; // 里程数
}

View File

@@ -0,0 +1,24 @@
package com.maibu.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 设备回调状态
*
* @author bill
*/
@Getter
@AllArgsConstructor
public enum DeviceStatusEnum {
working("1", "工作中"),
idle("2", "空闲中"),
offline("3", "离线"),
charging("4", "充电中"),
error("5", "故障");
String code;
String message;
}

View File

@@ -6,8 +6,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.util.CollectionUtils;
import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import com.maibu.influxdb.inter.InfluxField;

View File

@@ -0,0 +1,14 @@
package com.maibu.mapper;
import org.springframework.stereotype.Repository;
import com.maibu.core.host.LocationMessage;
import com.maibu.mybatis.mapper.BaseMapperX;
/**
* 设备历史状态
*/
@Repository
public interface HostLocationMapper extends BaseMapperX<LocationMessage> {
}

View File

@@ -1,6 +1,5 @@
package com.maibu.memory;
import cn.hutool.core.util.ObjectUtil;
import com.maibu.core.business.device.Connector;
import com.maibu.core.business.device.NettyDevice;
@@ -30,13 +29,15 @@ public class DeviceSessionManager {
// 设备ID -> 设备对象(业务实体)
private final ConcurrentHashMap<String, NettyDevice> deviceMap = new ConcurrentHashMap<>();
// 当前设备的控制权在哪 登录的账号 deviceId(下位机) -> token key 唯一保证一个deviceId 只有一个控制权
private final ConcurrentHashMap<String, String> controllerMap = 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;
@@ -64,7 +65,6 @@ public class DeviceSessionManager {
}
}
/**
* 注册设备连接(设备上线)
*
@@ -72,7 +72,8 @@ public class DeviceSessionManager {
* @param channel 通信通道
* @param deviceType 设备类型(MASTER/SLAVE)
*/
public void registerDevice(String deviceId, Channel channel, ConnectorType deviceType, ConnectorStatus status, String slaveId) {
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
@@ -84,6 +85,20 @@ public class DeviceSessionManager {
System.out.println("Registered device: " + deviceId + " type: " + deviceType);
}
/**
* 注册下位机设备
*
* @param deviceId 设备ID
*/
public void registerSlaveDevice(String deviceId, ConnectorStatus status, String slaveId) {
NettyDevice nettyDevice = deviceMap.computeIfAbsent(deviceId, id -> new NettyDevice(id, ConnectorType.SLAVE));
nettyDevice.setConnectorStatus(status); // 默认上线即ACTIVE
nettyDevice.setCurrentSlaveId(slaveId);
nettyDevice.setLatestLoginTime(System.currentTimeMillis());
deviceMap.put(deviceId, nettyDevice);
logger.debug("Registered slave device:{},type:{}", deviceId, ConnectorType.SLAVE);
}
/**
* 上位机绑定下位机,建立双向绑定关系
*
@@ -92,7 +107,8 @@ public class DeviceSessionManager {
*/
public void bindConnector(String masterId, String slaveId) {
NettyDevice master = deviceMap.get(masterId);
if (master == null) return;
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);
@@ -105,6 +121,62 @@ public class DeviceSessionManager {
return deviceMap.get(deviceId);
}
/**
* 获取有控制权的 客户端
*/
public String getMaster(String deviceId) {
return controllerMap.get(deviceId);
}
/**
* 获取客户端当前 控制的设备
*/
public String getControl(String token) {
for (String key : controllerMap.keySet()) {
if (controllerMap.get(key).equals(token)) {
return controllerMap.get(token);
}
}
return null;
}
public void lock(String deviceId, String token) {
if (!StringUtils.isEmpty(deviceId) && !StringUtils.isEmpty(token)) {
controllerMap.put(deviceId, token);
}
}
public boolean unLock(String token) {
if (!StringUtils.isEmpty(token)) {
Iterator<String> iterator = controllerMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if (controllerMap.get(key).equals(token)) {
iterator.remove();
}
}
}
return true;
}
public boolean unLock(String deviceId, String token, boolean force) {
if (!StringUtils.isEmpty(deviceId)) {
if (force) {
controllerMap.remove(deviceId);
return true;
} else {
String nowToken = controllerMap.get(deviceId);
if (!StringUtils.isEmpty(token) && nowToken.equals(token)) {
controllerMap.remove(deviceId);
return true;
}
}
}
return false;
}
// 模糊匹配
public List<NettyDevice> getDevicesLike(String query) {
return deviceMap.values().stream()
@@ -112,7 +184,6 @@ public class DeviceSessionManager {
.collect(Collectors.toList());
}
public List<NettyDevice> getSlaveControl(String slaveId) {
// 获取所有连接的主机 且是active状态的
return deviceMap.values().stream()
@@ -121,7 +192,6 @@ public class DeviceSessionManager {
.collect(Collectors.toList());
}
public List<NettyDevice> getAllSlaveControl(String slaveId) {
// 获取所有连接的主机 不区分状态
return deviceMap.values().stream()
@@ -137,7 +207,6 @@ public class DeviceSessionManager {
.findFirst().orElse(null);
}
/// 模糊匹配
public NettyDevice getUserControl(String userName) {
return deviceMap.values().stream()
@@ -146,7 +215,6 @@ public class DeviceSessionManager {
.findFirst().orElse(null);
}
/**
* 根据设备ID获取Channel
*/
@@ -161,7 +229,6 @@ public class DeviceSessionManager {
return channelToDeviceIdMap.get(channel);
}
/**
* 根据从机查控制的主机
*/
@@ -174,7 +241,8 @@ public class DeviceSessionManager {
* 根据从机查所有主机
*/
public List<NettyDevice> getMastersBySlaveId(String slaveId) {
return deviceMap.values().stream().filter(x -> Objects.equals(x.getCurrentSlaveId(), slaveId)).collect(Collectors.toList());
return deviceMap.values().stream().filter(x -> Objects.equals(x.getCurrentSlaveId(), slaveId))
.collect(Collectors.toList());
}
/**
@@ -182,18 +250,25 @@ public class DeviceSessionManager {
*/
public void removeDevice(String deviceId) {
NettyDevice nettyDevice = deviceMap.remove(deviceId);
// Channel channel = channelMap.remove(deviceId);
// Channel channel = channelMap.remove(deviceId);
clearChannelToDeviceIdMap(deviceId);
// if (channel != null) {
// channelToDeviceIdMap.remove(channel);
// channel.close();
// }
// 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);
}
// 清理channel和状态
nettyDevice.setChannel(null);
Channel channel = channelMap.remove(deviceId);
if (channel != null) {
channelToDeviceIdMap.remove(channel);
channel.close();
}
} else {
// 如果是下位机,则保留绑定关系,只清理channel和状态
nettyDevice.setChannel(null);
@@ -206,7 +281,6 @@ public class DeviceSessionManager {
}
}
/**
* 判断设备是否在线
*/
@@ -231,5 +305,20 @@ public class DeviceSessionManager {
return nettyDevice != null && nettyDevice.canReceive();
}
/**
* 获取客户端当前 控制的设备
*/
public String getDeviceByTaskId(Long taskId) {
if (taskId == null) {
return null;
}
for (String key : deviceMap.keySet()) {
NettyDevice nettyDevice = deviceMap.get(key);
if (nettyDevice != null && Objects.equals(nettyDevice.getCurrentTaskId(), taskId)) {
return nettyDevice.getConnectorId();
}
}
return null;
}
}

View File

@@ -1,17 +1,16 @@
package com.maibu.memory;
import com.maibu.constant.Constant;
import com.maibu.core.business.Device;
import com.maibu.core.business.IoTCommonDevice;
import com.maibu.core.business.path.LatAndLngEntity;
import com.maibu.influxdb.util.InfluxDBUtil;
import com.maibu.mqtt.MqttClientUtil;
import com.maibu.mqtt.*;
import lombok.Data;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
@@ -26,10 +25,13 @@ public class GlobalMemory {
// key:orgId - siteId
public static ConcurrentHashMap<Long, Map<Long, SiteMemory>> orgAttrs = new ConcurrentHashMap<>();
public static ConcurrentHashMap<Long, List<LatAndLngEntity>> taskPathMap = new ConcurrentHashMap<>();
public static InfluxDBUtil influxDBUtil;
public static MqttClientUtil mqttClientUtil;
// public static MqttClientUtil mqttClientUtil;
public static CustomMqttDeviceMonitor customMqttDeviceMonitor = null; //这里面包含了 连我们自己的mqtt 和外部的一些设备,自己的用clientId 是固定的
public static SiteMemory getSiteMemory(Long orgId, Long siteId) {
@@ -52,7 +54,7 @@ public class GlobalMemory {
/**
* 新增场站内存
*
*
* @param orgId
* @param siteId
* @param siteMemory
@@ -72,7 +74,7 @@ public class GlobalMemory {
/**
* 删除
*
*
* @param orgId
* @param siteId
*/
@@ -85,4 +87,35 @@ public class GlobalMemory {
}
}
public static void initMqttDeviceMonitor(String mqttHostUrl, String mqttClientId, String mqttUsername, String mqttPassword) {
if (customMqttDeviceMonitor == null) {
// 1. 创建 MQTT 监控器和消息处理器
customMqttDeviceMonitor = new CustomMqttDeviceMonitor();
// customMqttDeviceMonitor.registerHandler(new GenericPropertyMessageHandler());
customMqttDeviceMonitor.registerHandler(new HostMessageHandler()); //专门处理上位机的
// customMqttDeviceMonitor.registerHandler(new ActionMessageHandler());
//单独注册 对接上位机的
// 注册设备到 MQTT 监控器
CustomMqttDeviceConfig deviceConfig = new CustomMqttDeviceConfig();
deviceConfig.setDeviceKey(mqttClientId);
deviceConfig.setBrokerUrl(mqttHostUrl);
deviceConfig.setClientId(mqttClientId);
deviceConfig.setUsername(mqttUsername);
deviceConfig.setPassword(mqttPassword);
//TODO 添加所有需要订阅的
deviceConfig.setSubscribeTopics(Arrays.asList(
MqttTopic.MOWER_HEARTBEAT,//心跳
MqttTopic.MOWER_WILDCARD_LOCATION,//位置 经纬度
MqttTopic.MOWER_WILDCARD_TASK_ROUTE, //路径回复
MqttTopic.MOWER_WILDCARD_TASK_STATUS,//任务状态
MqttTopic.MOWER_WILDCARD_ERROR,//错误
MqttTopic.MOWER_WILDCARD_REALTIME, //状态消息
MqttTopic.MOWER_WILDCARD_TASK_TARGET_REPLY //路径规划下发回复
));
deviceConfig.setHandlerType(Constant.mqttHandleKey); //唯一的 用于处理上位机mqtt的
customMqttDeviceMonitor.registerDevice(deviceConfig);
}
}
}

View File

@@ -7,6 +7,7 @@ import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import com.maibu.core.business.*;
import com.maibu.core.business.dto.DeviceMapPushDTO;
import com.maibu.mqtt.CustomMqttDeviceMonitor;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttClient;
@@ -30,8 +31,6 @@ public class SiteMemory {
public SysSite sysSite;
public CustomMqttDeviceMonitor CustomMqttDeviceMonitor;
public static final ConcurrentHashMap<String, MqttClient> mqttClientMap = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<Long, OrderModel> orderModelMap = new ConcurrentHashMap<>();
@@ -55,6 +54,39 @@ public class SiteMemory {
// key deviceId execute
public static ConcurrentHashMap<String, DevicePlanTask> devicePlanTaskExecuteMap = new ConcurrentHashMap<>();
// 记录 设备上线后的运行数据 下线后清除并保存到数据库
public static ConcurrentHashMap<String, DeviceRunStatistics> deviceRunStatisticsMap = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, DeviceMapPushDTO> deviceMapPushMap = new ConcurrentHashMap<>();
public Map<String, DeviceMapPushDTO> getAllDeviceMapPush() {
return deviceMapPushMap;
}
public void saveMaPushDTO(String deviceId, DeviceMapPushDTO dto) {
deviceMapPushMap.put(deviceId, dto);
}
public void saveRunStatistics(DeviceRunStatistics deviceRunStatistics) {
if (deviceRunStatistics != null && !StringUtils.isEmpty(deviceRunStatistics.getDeviceId())) {
deviceRunStatisticsMap.put(deviceRunStatistics.getDeviceId(), deviceRunStatistics);
}
}
public DeviceRunStatistics getRunStatistics(String deviceId) {
if (!StringUtils.isEmpty(deviceId)) {
return deviceRunStatisticsMap.get(deviceId);
}
return null;
}
public void removeRunStatistics(String deviceId) {
if (!StringUtils.isEmpty(deviceId)) {
deviceRunStatisticsMap.remove(deviceId);
}
}
public static void saveDevice(Device device) {
deviceMap.put(device.getSerialNumber(), device);
}
@@ -137,7 +169,6 @@ public class SiteMemory {
}
}
public void saveDeviceRunParam(DeviceRunParam deviceRunParam) {
deviceRunParamMap.put(deviceRunParam.getDeviceId(), deviceRunParam);
}

View File

@@ -1,17 +1,24 @@
package com.maibu.mqtt;
import com.maibu.core.host.HeartBeatDTO;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义外部设备 MQTT 订阅管理器
@@ -66,6 +73,24 @@ public class CustomMqttDeviceMonitor {
*/
private ScheduledExecutorService scheduler;
private final ConcurrentHashMap<String, HeartBeatDTO> hearBeatMap = new ConcurrentHashMap<>();
private final ThreadPoolExecutor messageExecutor = new ThreadPoolExecutor(
4, 8, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(256),
new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "mqtt-message-worker-" + counter.getAndIncrement());
t.setDaemon(true);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
/**
* 巡检间隔(秒)
*/
@@ -74,7 +99,7 @@ public class CustomMqttDeviceMonitor {
// ==================== 消息处理器管理 ====================
public CustomMqttMessageHandler getMessageHandler(String key){
public CustomMqttMessageHandler getMessageHandler(String key) {
return handlerMap.get(key);
}
@@ -152,6 +177,25 @@ public class CustomMqttDeviceMonitor {
return client != null && client.isConnected();
}
/**
* 保存心跳
*/
public void saveHeartBeat(String sn, HeartBeatDTO dto) {
hearBeatMap.put(sn, dto);
}
/**
* 获取心跳
*/
public HeartBeatDTO getHeartBeat(String sn) {
return hearBeatMap.get(sn);
}
public List<HeartBeatDTO> getAllHeartBeat() {
return new ArrayList<>(hearBeatMap.values());
}
// ==================== 连接管理 ====================
/**
@@ -180,6 +224,7 @@ public class CustomMqttDeviceMonitor {
if (scheduler != null) {
scheduler.shutdown();
}
messageExecutor.shutdown();
clientMap.keySet().forEach(this::disconnectDevice);
log.info("CustomMqttDeviceMonitor 已停止");
}
@@ -226,7 +271,7 @@ public class CustomMqttDeviceMonitor {
public void messageArrived(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
log.debug("收到消息: deviceKey={}, topic={}, payload={}", deviceKey, topic, payload);
dispatchMessage(deviceKey, topic, payload, config);
messageExecutor.submit(() -> dispatchMessage(deviceKey, topic, payload, config));
}
@Override
@@ -373,6 +418,7 @@ public class CustomMqttDeviceMonitor {
/**
* 向指定设备发布消息(指定 QoS)
* 异步投递到线程池执行,避免阻塞 MQTT 回调线程
*/
public void publish(String deviceKey, String topic, String payload, int qos) throws MqttException {
MqttClient client = clientMap.get(deviceKey);
@@ -383,7 +429,7 @@ public class CustomMqttDeviceMonitor {
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(qos);
client.publish(topic, message);
log.debug("发布消息: deviceKey={}, topic={}", deviceKey, topic);
log.debug("发布消息: deviceKey={}, topic={},payload:{}", deviceKey, topic, payload);
}
/**

View File

@@ -0,0 +1,497 @@
package com.maibu.mqtt;
import java.util.ArrayList;
import java.util.List;
import com.maibu.core.business.dto.DeviceMapPushDTO;
import org.springframework.util.CollectionUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.maibu.constant.Constant;
import com.maibu.constant.NettyCacheKey;
import com.maibu.core.business.DeviceRunStatistics;
import com.maibu.core.business.device.DeviceStatusDetail;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.dto.WebDeviceTaskStatusMessageDTO;
import com.maibu.core.business.dto.WebStatusMessageDTO;
import com.maibu.core.business.inter.WebsocketMesDispather;
import com.maibu.core.enums.DeviceTaskStaus;
import com.maibu.core.enums.MesType;
import com.maibu.core.host.HeartBeatDTO;
import com.maibu.core.host.HostLocationRelTimeDTO;
import com.maibu.core.host.HostNavigationReplyDTO;
import com.maibu.core.host.HostTaskStatusDTO;
import com.maibu.core.host.HostVehicleRelTimeDTO;
import com.maibu.core.host.LocationMessage;
import com.maibu.core.redis.RedisCache;
import com.maibu.enums.DeviceStatusEnum;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.memory.GlobalMemory;
import com.maibu.memory.SiteMemory;
import com.maibu.utils.StringUtils;
import com.maibu.utils.json.JsonUtils;
import com.maibu.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
/**
* mqtt相关的 上位机处理器
*/
@Slf4j
public class HostMessageHandler implements CustomMqttMessageHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
private final DeviceSessionManager deviceSessionManager = SpringUtils.getBean(DeviceSessionManager.class);
private final WebsocketMesDispather websocketMesDispather = SpringUtils.getBean(WebsocketMesDispather.class);
private final String mqttClientId = "maibu-mqtt-client";
private final RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
@Override
public String getHandlerType() {
return Constant.mqttHandleKey;
}
@Override
public boolean supports(String handlerType) {
return Constant.mqttHandleKey.equals(handlerType);
}
/**
* 处理 MQTT 消息
*
* <p>
* 通配符订阅场景下,deviceKey 为配置中的固定标识(如 "host-monitor"),
* 真正的设备 ID 需从 topic 中解析,通过 {@link #extractDeviceId(String)} 获取。
* </p>
*/
@Override
public void handleMessage(String deviceKey, String topic, String payload, CustomMqttDeviceConfig config) {
try {
String deviceId = extractDeviceId(topic);
if (deviceId == null) {
log.warn("无法从topic解析deviceId: {}", topic);
return;
}
String action = extractAction(topic);
if (action == null) {
log.debug("未识别的topic: {}", topic);
return;
}
switch (action) {
case "location_post":
handleLocationPost(deviceId, topic, payload);
break;
case "realtime_post":
handleRealtimePost(deviceId, topic, payload);
break;
case "heartbeat_post":
handleHeartbeatPost(deviceId, topic, payload);
break;
case "error_post":
handleErrorPost(deviceId, topic, payload);
break;
// case "route_post":
// case "point_post":
// handleTaskPost(deviceId, topic, payload, action);
// break;
case "task_status_post":
handleTaskPost(deviceId, topic, payload, action);
break;
case "config_reply":
handleConfigReply(deviceId, topic, payload);
break;
case "control_reply":
handleControlReply(deviceId, topic, payload);
break;
case "route_reply":
handleRouteReply(deviceId, topic, payload);
break;
default:
log.debug("割草机消息: deviceId={}, topic={}, action={}", deviceId, topic, action);
break;
}
} catch (Exception e) {
log.error("消息处理异常: topic={}, error={}", topic, e.getMessage());
}
}
/**
* 定位信息 经纬度
*/
private void handleLocationPost(String deviceId, String topic, String payload) {
try {
log.debug("【位置数据】deviceId={}, payload={}", deviceId, payload);
HostLocationRelTimeDTO locationRelTimeDTO = JsonUtils.parseObject(payload, HostLocationRelTimeDTO.class);
if (locationRelTimeDTO != null) {
WebStatusMessageDTO dto = createWebDeviceLocationMessage(locationRelTimeDTO, deviceId);
List<NettyDevice> controlMasters = deviceSessionManager
.getAllSlaveControl(deviceId);
if (!CollectionUtils.isEmpty(controlMasters)) {
for (NettyDevice x : controlMasters) {
try {
websocketMesDispather.dispather(x.getConnectorId(), JsonUtils.toJsonString(dto));
} catch (Exception e) {
log.error("位置数据websocket推送失败: connectorId={}, error={}", x.getConnectorId(),
e.getMessage());
}
}
}
// todo 推送实时位置和状态消息到外部
// todo 记录到内存 开线程池队列 逐一转发
// GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
// String.format(MqttTopic.DEVICE_LOCATION_TOPIC, deviceId),
// JsonUtils.toJsonString(dto));
// todo 保存到内存
String key = NettyCacheKey.latestPositonKey + deviceId;
LocationMessage locationMessage = locationRelTimeDTO.getData();
locationMessage.setDeviceId(deviceId);
redisCache.setCacheObject(key, locationMessage);
}
} catch (Exception e) {
log.error("位置消息解析失败: deviceId={}, payload={}, error={}", deviceId, payload, e);
}
}
public WebStatusMessageDTO createWebDeviceLocationMessage(HostLocationRelTimeDTO locationRelTimeDTO,
String deviceId) {
WebStatusMessageDTO webStatusMessageDTO = new WebStatusMessageDTO();
List<DeviceStatusDetail> transferData = new ArrayList<>();
DeviceStatusDetail lat = new DeviceStatusDetail();
lat.setName("latitude");
lat.setValue(String.valueOf(locationRelTimeDTO.getData().getLatitude()));
lat.setUnit("");
transferData.add(lat);
DeviceStatusDetail lng = new DeviceStatusDetail();
lng.setName("longitude");
lng.setValue(String.valueOf(locationRelTimeDTO.getData().getLongitude()));
lng.setUnit("");
transferData.add(lng);
DeviceStatusDetail yaw = new DeviceStatusDetail();
yaw.setName("yaw");
yaw.setValue(String.valueOf(locationRelTimeDTO.getData().getYaw()));
yaw.setUnit("");
transferData.add(yaw);
DeviceStatusDetail fix_status = new DeviceStatusDetail();
fix_status.setName("fix_status");
fix_status.setValue(String.valueOf(locationRelTimeDTO.getData().getFix_status()));
fix_status.setUnit("");
transferData.add(fix_status);
DeviceStatusDetail altitude = new DeviceStatusDetail();
altitude.setName("altitude");
altitude.setValue(String.valueOf(locationRelTimeDTO.getData().getAltitude()));
altitude.setUnit("");
transferData.add(altitude);
DeviceStatusDetail status = new DeviceStatusDetail();
status.setName("status");
NettyDevice nettyDevice = deviceSessionManager.getDevice(deviceId);
// todo error故障
if (nettyDevice != null) {
if (nettyDevice.getOnlineStatus() == 0) {
status.setValue(DeviceStatusEnum.offline.getCode());
} else {
if (nettyDevice.getTask() != null && nettyDevice.getTask().isTaskAccepted()) {
status.setValue(DeviceStatusEnum.working.getCode());
} else {
status.setValue(DeviceStatusEnum.idle.getCode());
}
}
} else {
status.setValue(DeviceStatusEnum.offline.getCode());
}
status.setUnit("");
transferData.add(status);
webStatusMessageDTO.setData(transferData);
webStatusMessageDTO.setType(MesType.locationInfo);
if (nettyDevice != null && nettyDevice.getDevice() != null) {
Long siteId = nettyDevice.getDevice().getSiteId();
Long orgId = nettyDevice.getDevice().getOrgId();
SiteMemory siteMemory = GlobalMemory.getSiteMemory(orgId, siteId);
if (siteMemory != null) {
DeviceMapPushDTO dto = new DeviceMapPushDTO();
dto.setA(Double.parseDouble(altitude.getValue()));
dto.setId(deviceId);
// dto.setB();
// dto.setHa();
dto.setF(fix_status.getValue());
dto.setS(status.getValue());
dto.setLat(Double.parseDouble(lat.getValue()));
dto.setLng(Double.parseDouble(lng.getValue()));
dto.setT(nettyDevice.getCurrentTaskId());
dto.setY(Double.parseDouble(yaw.getValue()));
siteMemory.saveMaPushDTO(deviceId, dto);
}
}
return webStatusMessageDTO;
}
public WebStatusMessageDTO createWebDeviceRealtimeMessage(HostVehicleRelTimeDTO vehicleRelTimeDTO) {
// todo 状态消息
WebStatusMessageDTO webStatusMessageDTO = new WebStatusMessageDTO();
List<DeviceStatusDetail> transferData = new ArrayList<>();
DeviceStatusDetail voltage = new DeviceStatusDetail();
voltage.setName("voltage");
voltage.setValue(String.valueOf(vehicleRelTimeDTO.getData().getBattery_voltage()));
voltage.setUnit("");
transferData.add(voltage);
DeviceStatusDetail speed = new DeviceStatusDetail();
speed.setName("speed");
speed.setValue(String.valueOf(vehicleRelTimeDTO.getData().getSpeed()));
speed.setUnit("");
transferData.add(speed);
DeviceStatusDetail motion = new DeviceStatusDetail();
motion.setName("motion");
motion.setValue(String.valueOf(vehicleRelTimeDTO.getData().getMotion()));
motion.setUnit("");
transferData.add(motion);
DeviceStatusDetail percentage = new DeviceStatusDetail();
percentage.setName("percentage");
percentage.setValue(String.valueOf(vehicleRelTimeDTO.getData().getBattery_percentage()));
percentage.setUnit("");
transferData.add(percentage);
webStatusMessageDTO.setData(transferData);
webStatusMessageDTO.setType(MesType.deviceInfo);
return webStatusMessageDTO;
}
/**
* 上位机数据
*/
private void handleRealtimePost(String deviceId, String topic, String payload) {
log.debug("【实时数据】deviceId={}, payload={}", deviceId, payload);
try {
HostVehicleRelTimeDTO vehicleRelTimeDTO = JsonUtils.parseObject(payload, HostVehicleRelTimeDTO.class);
if (vehicleRelTimeDTO != null) {
// todo 更新里程数 里程数 * 割幅0.8
vehicleRelTimeDTO.getData().getDistance();
NettyDevice nettyDevice = deviceSessionManager.getDevice(deviceId);
if (nettyDevice != null) {
Long orgId = nettyDevice.getDevice().getOrgId();
Long siteId = nettyDevice.getDevice().getSiteId();
SiteMemory siteMemory = GlobalMemory.getSiteMemory(orgId, siteId);
if (siteMemory != null) {
DeviceRunStatistics deviceRunStatistics = siteMemory.getRunStatistics(deviceId);
if (deviceRunStatistics == null) {
deviceRunStatistics = new DeviceRunStatistics();
}
deviceRunStatistics.setDeviceId(deviceId);
deviceRunStatistics.setDistance(vehicleRelTimeDTO.getData().getDistance());
deviceRunStatistics.setWorkArea(vehicleRelTimeDTO.getData().getDistance() * 0.8);
siteMemory.saveRunStatistics(deviceRunStatistics);
}
}
WebStatusMessageDTO dto = createWebDeviceRealtimeMessage(vehicleRelTimeDTO);
List<NettyDevice> controlMasters = deviceSessionManager
.getAllSlaveControl(deviceId);
if (!CollectionUtils.isEmpty(controlMasters)) {
for (NettyDevice x : controlMasters) {
try {
websocketMesDispather.dispather(x.getConnectorId(), JsonUtils.toJsonString(dto));
} catch (Exception e) {
log.error("实时数据websocket推送失败: connectorId={}, error={}", x.getConnectorId(),
e.getMessage());
}
}
}
}
} catch (Exception e) {
log.error("实时消息解析失败: deviceId={}, payload={}, error={}", deviceId, payload, e.getMessage());
}
}
/**
* 处理心跳
* topic: mower/{deviceId}/event/heartbeat/post
*/
private void handleHeartbeatPost(String deviceId, String topic, String payload) {
log.debug("【心跳】payload={}", payload);
// 心跳处理
HeartBeatDTO beatDTO = JsonUtils.parseObject(payload, HeartBeatDTO.class);
if (beatDTO != null) {
beatDTO.setSysTimestamp(System.currentTimeMillis());
GlobalMemory.customMqttDeviceMonitor.saveHeartBeat(beatDTO.getSn(), beatDTO);
}
}
/**
* 处理错误告警
* topic: mower/{deviceId}/event/error/post
*/
private void handleErrorPost(String deviceId, String topic, String payload) {
log.warn("【错误告警】deviceId={}, payload={}", deviceId, payload);
}
/**
* 处理任务相关上报
*/
private void handleTaskPost(String deviceId, String topic, String payload, String action) {
log.info("【任务上报】deviceId={}, action={}, payload={}", deviceId, action, payload);
HostTaskStatusDTO dto = JsonUtils.parseObject(payload, HostTaskStatusDTO.class);
if (dto != null && !StringUtils.isEmpty(dto.getStatus())) {
NettyDevice nettyDevice = deviceSessionManager.getDevice(deviceId);
if ("completed".equals(dto.getStatus())) {
Long taskId = nettyDevice.finishTask();
WebDeviceTaskStatusMessageDTO sendDto = new WebDeviceTaskStatusMessageDTO();
sendDto.setDeviceId(deviceId);
sendDto.setTaskId(taskId);
sendDto.setStatus(DeviceTaskStaus.FINISH.toString());
sendDto.setType(MesType.device_task_change);
List<NettyDevice> controlMasters = deviceSessionManager
.getAllSlaveControl(deviceId);
if (!CollectionUtils.isEmpty(controlMasters)) {
for (NettyDevice x : controlMasters) {
try {
websocketMesDispather.dispather(x.getConnectorId(), JsonUtils.toJsonString(sendDto));
} catch (Exception e) {
log.error("实时数据websocket推送失败: connectorId={}, error={}", x.getConnectorId(),
e.getMessage());
}
}
}
}
if ("cancelled".equals(dto.getStatus())) {
nettyDevice.canceledTask();
}
// if("paused".equals(dto.getStatus())){
// NettyDevice nettyDevice = deviceSessionManager.getDevice(deviceId);
// nettyDevice.finishTask();
// }
if ("failed".equals(dto.getStatus())) {
nettyDevice.failedTask();
}
// if("accepted".equals(dto.getStatus())){
// NettyDevice nettyDevice = deviceSessionManager.getDevice(deviceId);
// nettyDevice.finishTask();
// }
// if("running".equals(dto.getStatus())){
// NettyDevice nettyDevice = deviceSessionManager.getDevice(deviceId);
// nettyDevice.finishTask();
// }
}
}
/**
* 处理配置响应
*/
private void handleConfigReply(String deviceId, String topic, String payload) {
log.debug("【配置响应】deviceId={}, payload={}", deviceId, payload);
}
/**
* 处理控制指令响应
*/
private void handleControlReply(String deviceId, String topic, String payload) {
log.debug("【控制响应】deviceId={}, payload={}", deviceId, payload);
}
/**
* 处理控制指令响应
*/
private void handleRouteReply(String deviceId, String topic, String payload) {
log.debug("【路径任务下发响应】deviceId={}, payload={}", deviceId, payload);
// todo 更新任务接收状态
HostNavigationReplyDTO replyDTO = JsonUtils.parseObject(payload, HostNavigationReplyDTO.class);
NettyDevice device = deviceSessionManager.getDevice(deviceId);
if (replyDTO.isAccepted()) {
if (device != null && device.getTask() != null
&& String.valueOf(device.getTask().getId()).equals(replyDTO.getTask_id())) {
device.getTask().setTaskAccepted(true);
}
}
}
/**
* 从 Topic 中解析 deviceId
* Topic 格式:mower/{deviceId}/...
*/
private String extractDeviceId(String topic) {
if (topic == null || topic.isEmpty()) {
return null;
}
String[] parts = topic.split("/");
if (parts.length < 2 || !"mower".equals(parts[0])) {
return null;
}
return parts[1];
}
/**
* 从 Topic 中提取动作标识
* 将 mower/{deviceId}/xxx/yyy/post 转为 xxx_yyy_post 形式
*/
private String extractAction(String topic) {
String[] parts = topic.split("/");
if (parts.length < 3 || !"mower".equals(parts[0])) {
return null;
}
StringBuilder path = new StringBuilder();
for (int i = 2; i < parts.length; i++) {
if (i > 2)
path.append("/");
path.append(parts[i]);
}
String remaining = path.toString();
if (remaining.equals("property/location/post"))
return "location_post";
if (remaining.equals("property/realtime/post"))
return "realtime_post";
if (remaining.equals("event/heartbeat/post"))
return "heartbeat_post";
if (remaining.equals("event/error/post"))
return "error_post";
// if (remaining.equals("task/route/post")) return "route_post";
// if (remaining.equals("task/point/post")) return "point_post";
if (remaining.equals("task/status/post"))
return "task_status_post";
if (remaining.equals("action/config/reply"))
return "config_reply";
if (remaining.equals("action/control/reply"))
return "control_reply";
if (remaining.equals("task/target/reply"))
return "route_reply";
return null;
}
@Override
public void onConnectionLost(String deviceKey, Throwable cause) {
log.warn("主机监控连接断开: deviceKey={}, reason={}", deviceKey, cause.getMessage());
}
@Override
public void onSubscribed(String deviceKey, String topic) {
log.info("主机监控订阅主题: deviceKey={}, topic={}", deviceKey, topic);
}
}

View File

@@ -1,16 +1,18 @@
package com.maibu.mqtt;
public class MqttTopic {
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
public static final String DEVICE_STATUS_TOPIC = "device/%s/realTimeMessage";
public static final String DEVICE_LOCATION_TOPIC = "device/%s/locationMessage";
/** @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_ROUTE_PUSH_TOPIC = "device/%s/route/post";
/** 产品标识:割草机 */
public static final String MOWER_PRODUCT = "mower";
@@ -18,12 +20,12 @@ public class MqttTopic {
// ==================== 设备连接与状态 ====================
/** 设备上线/下线事件上报(设备->平台),对应 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";
// ==================== 实时运行数据 ====================
/** 实时运行数据上报:电压/速度/温度/位置等(设备->平台),对应 TCP 0x02 status */
public static final String MOWER_REALTIME_POST = "mower/%s/property/realtime/post";
/** GPS 位置数据上报:经纬度、速度、航向等(设备->平台) */
public static final String MOWER_LOCATION_POST = "mower/%s/property/location/post";
// ==================== 心跳保活 ====================
/** 设备心跳上报(设备->平台),对应 TCP 0xFF heartbeat */
@@ -43,58 +45,64 @@ public class MqttTopic {
/** 远程控制指令下发:前进/后退/转向/割草等(平台->设备),对应 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";
// 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";
public static final String MOWER_TASK_ROUTE_SET = "mower/%s/task/target/set";
/** 启动任务执行(平台->设备),对应 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";
// /** 下发 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_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_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_LOCATION = "mower/+/property/location/post";
/** 订阅所有设备的错误告警 */
public static final String MOWER_WILDCARD_ERROR = "mower/+/event/error/post";
/** 订阅所有设备的任务状态变更 */
public static final String MOWER_WILDCARD_TASK_STATUS = "mower/+/task/status/post";
public static final String MOWER_WILDCARD_TASK_ROUTE = "mower/+/task/route/post";
public static final String MOWER_WILDCARD_TASK_TARGET_REPLY = "mower/+/task/target/reply";
public static final String MOWER_HEARTBEAT = "mower/+/event/heartbeat/post";
/**
* 格式化 Topic 模板,将 %s 替换为设备ID
*

View File

@@ -29,6 +29,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
@@ -58,6 +59,9 @@ public class UAVService {
@Autowired
private RedisCache redisCache;
@Autowired
private ScheduledExecutorService executor;
public static final Map<String, Long> latestActiveTime = new ConcurrentHashMap<>();
@@ -669,7 +673,6 @@ public class UAVService {
@PostConstruct
public void heartbeatMonitor() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(() -> {
try {
Long nowTime = System.currentTimeMillis();
@@ -821,4 +824,5 @@ public class UAVService {
return false;
}
}

View File

@@ -116,7 +116,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/api/thermal/**","/iotDA/device/**", "/websocket/allMemory", "/iot/UAV/**", "/iot/api/**", "/iot/device/userDevice", "/external/sms/sendAliSmsCode", "/login", "/register", "/captchaImage", "/iot/tool/register", "/iot/tool/ntp",
.antMatchers("/iot/host/upload-path-csv","/api/thermal/**","/iotDA/device/**", "/websocket/allMemory", "/iot/UAV/**", "/iot/api/**", "/iot/device/userDevice", "/external/sms/sendAliSmsCode", "/login", "/register", "/captchaImage", "/iot/tool/register", "/iot/tool/ntp",
"/iot/tool/mqtt/auth", "/iot/workRecord/**", "/iot/tool/mqtt/authv5", "/iot/tool/mqtt/webhook", "/iot/tool/mqtt/webhookv5", "/auth/**/**",
"/wechat/mobileLogin", "/wechat/miniLogin", "/wechat/wxBind/callback").permitAll()
.antMatchers("/zlmhook/**").permitAll()

View File

@@ -170,8 +170,9 @@ public class TokenService {
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return createToken(claims);
String realToken = createToken(claims);
loginUser.setRealToken(realToken);
return realToken;
}
public int addToken(SysUser user, SysClient sysClient) {

View File

@@ -6,7 +6,7 @@ import com.huaweicloud.sdk.iot.device.client.requests.CommandRsp;
import com.huaweicloud.sdk.iot.device.client.requests.DeviceMessage;
import com.huaweicloud.sdk.iot.device.client.requests.ServiceProperty;
import com.huaweicloud.sdk.iot.device.transport.ActionListener;
import com.maibu.common.NettyCacheKey;
import com.maibu.constant.NettyCacheKey;
import com.maibu.core.business.DeviceRunningStatusHistory;
import com.maibu.core.business.DeviceStatusRecordDTO;
import com.maibu.core.redis.RedisCache;

View File

@@ -3,6 +3,7 @@ package com.maibu.controller;
import java.time.LocalDate;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
@@ -45,10 +46,10 @@ public class DeviceTaskController extends BaseController {
@GetMapping("/getSiteDevicePlan")
public AjaxResult getSiteDevicePlan(@RequestParam Long siteId,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startTime,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endTime,
@RequestParam(required = false) String deviceId,
@RequestParam(required = false) DeviceTaskStaus taskStaus) {
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startTime,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endTime,
@RequestParam(required = false) String deviceId,
@RequestParam(required = false) DeviceTaskStaus taskStaus) {
return AjaxResult.success(deviceTaskService.getSiteDevicePlan(siteId, startTime, endTime, deviceId, taskStaus));
}
@@ -85,7 +86,6 @@ public class DeviceTaskController extends BaseController {
return AjaxResult.success(deviceTaskService.concurrentDevicePlanTask(deviceId));
}
@GetMapping("/workStatistics")
public AjaxResult workStatistics(@RequestParam Long userId) {
return AjaxResult.success(deviceTaskService.workStatistics(userId));
@@ -93,13 +93,13 @@ public class DeviceTaskController extends BaseController {
@PostMapping("/deviceTaskPool")
public TableDataInfo deviceTaskPool(@RequestBody DeviceTaskQueryDTO dto) {
startPostPage(dto.getPageNum(),dto.getPageSize());
startPostPage(dto.getPageNum(), dto.getPageSize());
return getDataTable(deviceTaskService.deviceTaskPool(dto));
}
@PostMapping("/getSiteDeviceTaskHistory")
public TableDataInfo getSiteDeviceTaskHistory(@RequestBody DeviceTaskQueryDTO dto) {
startPostPage(dto.getPageNum(),dto.getPageSize());
startPostPage(dto.getPageNum(), dto.getPageSize());
return getDataTable(deviceTaskService.getSiteDeviceTaskHistory(dto));
}
@@ -124,13 +124,38 @@ public class DeviceTaskController extends BaseController {
return AjaxResult.success(deviceTaskService.createDeviceTask(dto, getLoginUser().getUsername()));
}
@PostMapping("/createRegionDeviceTask")
public AjaxResult createRegionDeviceTask(@RequestBody DeviceTaskCommandDTO dto) {
String deviceId = dto.getDeviceId();
Long siteId = dto.getSiteId();
Long orgId = dto.getOrgId();
Long regionId = dto.getRegionId();
if (StringUtils.isEmpty(deviceId)) {
return AjaxResult.error("deviceId为空!");
}
if (siteId == null) {
return AjaxResult.error("siteId为空!");
}
if (orgId == null) {
return AjaxResult.error("orgId为空!");
}
if (regionId == null) {
return AjaxResult.error("regionId为空!");
}
SiteMemory siteMemory = GlobalMemory.getSiteMemory(orgId, siteId);
if (siteMemory.hasTask(deviceId)) {
return AjaxResult.error("当前设备存在任务!");
}
return AjaxResult.success(deviceTaskService.createRegionDeviceTask(dto, getLoginUser().getUsername()));
}
@PostMapping("/cancelTask")
public AjaxResult cancelTask(@RequestBody DeviceTaskCommandDTO dto) {
public AjaxResult cancelTask(@RequestBody DeviceTaskCommandDTO dto) throws MqttException {
String deviceId = dto.getDeviceId();
if (StringUtils.isEmpty(deviceId) && dto.getTaskId() == null) {
return AjaxResult.error("deviceId或taskId为空!");
}
return AjaxResult.success(deviceTaskService.cancelTask(dto, getUsername()));
return AjaxResult.success(deviceTaskService.cancelTask(dto));
}
@PostMapping("/pauseTask")
@@ -151,5 +176,4 @@ public class DeviceTaskController extends BaseController {
return AjaxResult.success(deviceTaskService.recoveryTask(dto, getUsername()));
}
}

View File

@@ -66,11 +66,7 @@ public class TransferDeviceController extends BaseController {
if (StringUtils.isEmpty(bindDTO.getPlatform())) {
return AjaxResult.error("平台为空!");
}
LoginUser user = getLoginUser();
if (user == null) {
return AjaxResult.error("当前token用户不存在!");
}
return AjaxResult.success(transferDeviceService.switchDevice(bindDTO, user));
return AjaxResult.success(transferDeviceService.switchDevice(bindDTO));
}
/**
@@ -87,8 +83,7 @@ public class TransferDeviceController extends BaseController {
if (StringUtils.isEmpty(bindDTO.getPlatform())) {
return AjaxResult.error("平台为空!");
}
LoginUser user = getLoginUser();
return AjaxResult.success(transferDeviceService.remoteControl(bindDTO, user));
return AjaxResult.success(transferDeviceService.remoteControl(bindDTO));
} catch (Exception e) {
logger.error("获取设备控制权出错", e);
return AjaxResult.error();
@@ -122,8 +117,7 @@ public class TransferDeviceController extends BaseController {
if (StringUtils.isEmpty(bindDTO.getPlatform())) {
return AjaxResult.error("平台为空!");
}
LoginUser user = getLoginUser();
transferDeviceService.releaseControl(bindDTO, user);
transferDeviceService.releaseControl(bindDTO);
return AjaxResult.success();
}
}

View File

@@ -24,4 +24,6 @@ public class DeviceBindDTO {
private Integer type; // releaseControl 1 完全释放 2 释放控制权 可监听数据
private String token;
}

View File

@@ -39,6 +39,12 @@ public class InitThread implements ApplicationRunner {
@Resource(name = FastBeeConstant.TASK.DEVICE_ERROR_MONITOR)
private Executor deviceErrorMonitorExecutor;
@Resource(name = FastBeeConstant.TASK.DEVICE_HEARTBEAT_MONITOR)
private Executor deviceHeartBeatMonitorExecutor;
@Resource(name = FastBeeConstant.TASK.MAP_MESSAGE_PUSH)
private Executor mapMessagePushExecutor;
@Resource(name = FastBeeConstant.TASK.COMMON_DEVICE_MONITOR)
private Executor commonDeviceMonitorExecutor;
@@ -121,8 +127,19 @@ public class InitThread implements ApplicationRunner {
private void init() throws MqttException {
// 初始化InfluxDBUtil
GlobalMemory.influxDBUtil = new InfluxDBUtil(influxDBUrl, influxDBToken, influxDBOrg, influxDBBucket);
// 初始化MqttClientUtil
GlobalMemory.mqttClientUtil = new MqttClientUtil(mqttHostUrl, mqttClientId, mqttUsername, mqttPassword);
GlobalMemory.initMqttDeviceMonitor(mqttHostUrl, mqttClientId, mqttUsername, mqttPassword);
// 地图监控推送
mapMessagePushExecutor.execute(() -> {
deviceThreadService.mapMessagePush();
});
// 上位机设备心跳监控
deviceHeartBeatMonitorExecutor.execute(() -> {
deviceThreadService.heartBeatMonitor();
});
// 设备错误监听线程
deviceErrorMonitorExecutor.execute(() -> {
try {

View File

@@ -4,10 +4,13 @@ import java.util.List;
import java.util.Set;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.memory.GlobalMemory;
import com.maibu.mqtt.MqttTopic;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@@ -32,6 +35,9 @@ public class CommandForwardHandler extends SimpleChannelInboundHandler<String> {
@Autowired
private DeviceSessionManager deviceSessionManager;
@Value("${mqtt.client-id}")
private String mqttClientId;
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel currentChannel = ctx.channel();
@@ -78,11 +84,14 @@ public class CommandForwardHandler extends SimpleChannelInboundHandler<String> {
logger.debug("发送拒绝master:{},消息到达的slave设备:{},处于禁止接收状态", senderDeviceId, target.getConnectorId());
continue;
}
Channel targetChannel = target.getChannel();
if (targetChannel != null && targetChannel.isActive()) {
targetChannel.writeAndFlush(msg);
logger.debug("从master:{},向slave:{}, 转发消息:{}", senderDeviceId, target.getConnectorId(), msg);
}
// todo 向上位机发送控制消息
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId, String.format(MqttTopic.MOWER_CONTROL_SET, target.getConnectorId()), msg);
// Channel targetChannel = target.getChannel();
// if (targetChannel != null && targetChannel.isActive()) {
// targetChannel.writeAndFlush(msg);
// logger.debug("从master:{},向slave:{}, 转发消息:{}", senderDeviceId, target.getConnectorId(), msg);
// }
}
} else if (ConnectorType.SLAVE.equals(senderNettyDevice.getDeviceType())) {
List<NettyDevice> masters = deviceSessionManager.getMastersBySlaveId(senderDeviceId);

View File

@@ -13,18 +13,17 @@ import com.maibu.constant.Constant;
import com.maibu.core.business.DeviceRunParam;
import com.maibu.mapper.DeviceRunParamMapper;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.memory.SiteMemory;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.maibu.constant.CommandConstant;
import com.maibu.common.NettyCacheKey;
import com.maibu.constant.NettyCacheKey;
import com.maibu.core.business.DeviceRunStatistics;
import com.maibu.core.business.DeviceRunningStatusHistory;
import com.maibu.core.business.DeviceStatusRecordDTO;
@@ -33,7 +32,7 @@ import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.inter.WebsocketMesDispather;
import com.maibu.core.enums.MesType;
import com.maibu.core.redis.RedisCache;
import com.maibu.dto.WebStatusMessageDTO;
import com.maibu.core.business.dto.WebStatusMessageDTO;
import com.maibu.influxdb.MowerRealTimeData;
import com.maibu.influxdb.util.InfluxSqlBuilder;
import com.maibu.memory.GlobalMemory;
@@ -63,6 +62,9 @@ public class DataToDataBaseHandler extends SimpleChannelInboundHandler<String> {
@Autowired
private DeviceRunParamMapper deviceRunParamMapper;
@Value("${mqtt.client-id}")
private String mqttClientId;
private static final Long recordInterval = 5000L; // 5秒记录一次
// private static final Long pushInterval = 500L; // 0.5秒推送一次
@@ -96,13 +98,9 @@ public class DataToDataBaseHandler extends SimpleChannelInboundHandler<String> {
String deviceStatus = new String(bytes, 2, bytes.length - 4, StandardCharsets.UTF_8).trim();
String[] split = deviceStatus.split(",");
WebStatusMessageDTO dto = createWebDeviceStatusMessage(split);
try {
GlobalMemory.mqttClientUtil.publish(
String.format(MqttTopic.DEVICE_STATUS_TOPIC, deviceID),
JsonUtils.toJsonString(dto));
} catch (MqttException e) {
logger.error("发送实时状态数据错误: {}", e.getMessage());
}
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
String.format(MqttTopic.DEVICE_STATUS_TOPIC, deviceID),
JsonUtils.toJsonString(dto));
List<NettyDevice> controlMasters = deviceSessionManager
.getAllSlaveControl(deviceID);
if (!CollectionUtils.isEmpty(controlMasters)) {
@@ -136,19 +134,15 @@ public class DataToDataBaseHandler extends SimpleChannelInboundHandler<String> {
latestStatus.setDeviceId(deviceID);
} else {
latestStatus = nettyDevice.getLatestStatus();
// 使用Duration.between计算两个时间点之间的持续时间
Duration duration = Duration.between(latestStatus.getStartTime(), LocalDateTime.now());
long minutes = duration.toMinutes();
latestStatus.setTime(minutes);
}
latestStatus.setWorkArea(Double.valueOf(workArea));
latestStatus.setDistance(Double.valueOf(workArea) / 0.8);
nettyDevice.setLatestStatus(latestStatus);
}
} else if (bytes[2] == CommandConstant.readConfig) {
DeviceRunParam runParam = DeviceRunParam.fromBytes(bytes);
logger.debug("收到返回的设备运行参数: {}", JsonUtils.toJsonString(runParam));
CompletableFuture<DeviceRunParam> future =
ctx.channel().attr(Constant.READ_CONFIG_KEY).get();
CompletableFuture<DeviceRunParam> future = ctx.channel().attr(Constant.READ_CONFIG_KEY).get();
if (future != null) {
future.complete(runParam);
ctx.channel().attr(Constant.READ_CONFIG_KEY).set(null);
@@ -160,7 +154,6 @@ public class DataToDataBaseHandler extends SimpleChannelInboundHandler<String> {
}
}
public WebStatusMessageDTO createWebDeviceStatusMessage(String[] split) {
WebStatusMessageDTO webStatusMessageDTO = new WebStatusMessageDTO();
List<DeviceStatusDetail> transferData = transferStatusData(split);

View File

@@ -8,23 +8,23 @@ import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Resource;
import com.maibu.memory.DeviceSessionManager;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.maibu.constant.CommandConstant;
import com.maibu.constant.Constant;
import com.maibu.core.business.Device;
import com.maibu.core.business.DeviceRunStatistics;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.inter.WebsocketMesDispather;
import com.maibu.core.domain.entity.SysUser;
import com.maibu.core.domain.entity.UserClient;
import com.maibu.core.enums.CommandRequestType;
import com.maibu.core.enums.ConnectionCloseReason;
import com.maibu.core.enums.ConnectorStatus;
@@ -33,12 +33,12 @@ import com.maibu.core.enums.DeviceStatus;
import com.maibu.core.enums.DeviceTaskStaus;
import com.maibu.core.enums.MesType;
import com.maibu.core.enums.RespondCode;
import com.maibu.dto.DeviceLoginResponseDTO;
import com.maibu.dto.DeviceRequestDTO;
import com.maibu.dto.DeviceRespondDTO;
import com.maibu.dto.DeviceStatusChangeDTO;
import com.maibu.dto.DeviceTaskStatusMessageDTO;
import com.maibu.mapper.DeviceRunStatisticsMapper;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.memory.GlobalMemory;
import com.maibu.mqtt.MqttTopic;
import com.maibu.service.IDeviceService;
@@ -73,15 +73,15 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
@Autowired
private IDeviceService deviceService;
@Autowired
private ISysUserService userService;
@Autowired
private DeviceRunStatisticsMapper deviceRunStatisticsMapper;
@Resource
private WebsocketMesDispather websocketMesDispather;
@Value("${mqtt.client-id}")
private String mqttClientId;
public DeviceConnectHandler(DeviceSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@@ -115,6 +115,8 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
logger.info("{}: 设备断开 status: {}", deviceId, ConnectorStatus.DISABLED);
// updateLogoutSingle(deviceId);
// todo 自动切换到其他?
sessionManager.unLock(deviceId);
}
if (nettyDevice != null && nettyDevice.getDeviceType() == ConnectorType.SLAVE) {
nettyDevice.setOnlineStatus(0);
@@ -143,8 +145,24 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
// todo 更新历史作业消息
}
if (nettyDevice.getLatestStatus() != null) {
nettyDevice.getLatestStatus().setEndTime(LocalDateTime.now());
deviceRunStatisticsMapper.insert(nettyDevice.getLatestStatus());
LambdaQueryWrapper<DeviceRunStatistics> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeviceRunStatistics::getDeviceId, deviceId);
DeviceRunStatistics existing = deviceRunStatisticsMapper
.selectOne(queryWrapper);
if (existing != null) {
nettyDevice.getLatestStatus().setId(existing.getId());
nettyDevice.getLatestStatus().setDistance(
existing.getDistance() + nettyDevice.getLatestStatus().getDistance());
nettyDevice.getLatestStatus().setWorkArea(
existing.getWorkArea() + nettyDevice.getLatestStatus().getWorkArea());
long time = System.currentTimeMillis() - nettyDevice.getLatestLoginTime();
nettyDevice.getLatestStatus().setTime(existing.getTime() + time / (1000 * 60)); // 累加时长,单位为分钟
} else {
long time = System.currentTimeMillis() - nettyDevice.getLatestLoginTime();
nettyDevice.getLatestStatus().setTime(time / (1000 * 60)); // 累加时长,单位为分钟
}
deviceRunStatisticsMapper.saveOrUpdate(nettyDevice.getLatestStatus());
}
}
sessionManager.removeDevice(deviceId);
@@ -155,7 +173,7 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
try {
byte[] bytes = Hex.decodeHex(msg.toCharArray());
@@ -181,86 +199,13 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
logger.info("上位机连接 sessionId: {}", sessionId);
logger.info("上位机连接 userName: {}", userName);
logger.info("上位机连接 type: {}", parts[1]);
SysUser sysUser = userService.selectUserByUserName(userName);
if (sysUser != null) {
sysUserClientService.insertUserClient(sysUser.getUserId(), userName, deviceId, "", 0);
}
// 如果 当前角色存在sessionId同时和当前的穿过来不同则下发剔除指令
boolean t = false;
if (StringUtils.isEmpty(sessionId) || sysUser == null) {
t = true;
}
if (sysUser != null && !StringUtils.isEmpty(sessionId)
&& !StringUtils.isEmpty(sysUser.getSessionId())) {
if (!sysUser.getSessionId().equals(sessionId)) {
t = true;
}
}
if (t) {
// 发送已登录状态到客户端
if ("web".equals(parts[1])) {
NettyDevice device = sessionManager.getDevice(deviceId);
logger.info("web 上位机连接重连: {}", JsonUtils.toJsonString(device));
if (device != null && !StringUtils.isEmpty(device.getCurrentSlaveId())) {
logger.info("上位机连接重连: {}", deviceId);
} else {
// 判断是否账号在控制空 如果在控制设置账号为非active
List<UserClient> userClients = sysUserClientService
.selectUserClientByUserName(userName);
ConnectorStatus status = ConnectorStatus.ACTIVE;
logger.info("web 上位机连接重连2: {}", JsonUtils.toJsonString(userClients));
UserClient nowClient = null;
if (!CollectionUtils.isEmpty(userClients)) {
long count = userClients.stream().filter(
x -> x.getIsAlive() == 1 && !deviceId.equals(x.getClientName()))
.count();
if (count > 0) {
status = ConnectorStatus.RECEIVER;
}
nowClient = userClients.stream().filter(
x -> x.getIsAlive() == 1 && deviceId.equals(x.getClientName()))
.findFirst().orElse(null);
}
logger.info("web 上位机连接重连3: {}", JsonUtils.toJsonString(status));
if (!ConnectorStatus.ACTIVE.equals(status)) {
handleMasterConnect(ctx, deviceId, null, status);
} else {
logger.info("web 上位机连接重连4: {}", JsonUtils.toJsonString(nowClient));
if (nowClient != null) {
handleMasterConnect(ctx, deviceId, nowClient.getDeviceName(), status);
} else {
handleMasterConnect(ctx, deviceId, null, status);
}
}
}
} else {
DeviceLoginResponseDTO responseDTO = new DeviceLoginResponseDTO();
responseDTO.setRespond(RespondCode.have_logged_in);
ByteBuf buf = ctx.alloc().buffer();
CommandUtils.buildCommand(buf, responseDTO, CommandConstant.interaction);
ctx.channel().writeAndFlush(buf);
}
NettyDevice masterDevice = sessionManager.getDevice(sessionId);
if (masterDevice == null) {
// sessionManager.getControl(sessionId);
handleMasterConnect(ctx, sessionId, null, ConnectorStatus.ACTIVE);
} else {
// 判断是否账号在控制空 如果在控制设置账号为非active
List<UserClient> userClients = sysUserClientService
.selectUserClientByUserName(userName);
ConnectorStatus status = ConnectorStatus.ACTIVE;
if (!CollectionUtils.isEmpty(userClients)) {
long count = userClients.stream()
.filter(x -> x.getIsAlive() == 1 && !deviceId.equals(x.getClientName()))
.count();
if (count > 0) {
status = ConnectorStatus.RECEIVER;
}
}
logger.info("上位机上线:{}", status);
// if (ConnectorStatus.ACTIVE.equals(status)) {
// //todo 设置当前控制的token
// sysUser.setSessionId(sessionId);
// userService.updateUserProfile(sysUser);
// }
handleMasterConnect(ctx, deviceId, null, status);
ctx.channel().attr(Constant.ATT_DEVICE_ID).set(sessionId);
}
}
} else {
@@ -279,11 +224,13 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
device.setProductName("割草机产品MC700");
device.setCreateTime(LocalDateTime.now());
device.setUpdateTime(LocalDateTime.now());
device.setHasHost(false);
deviceService.insertDeviceBySelf(device);
} else {
device.setStatus(3);
device.setOnlineStatus(DeviceStatus.online.getType());
device.setUpdateTime(LocalDateTime.now());
device.setHasHost(false);
deviceService.updateDeviceBySelf(device);
}
NettyDevice slaveDevice = sessionManager.getDevice(data);
@@ -291,23 +238,24 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
slaveDevice.setOnlineStatus(1);
slaveDevice.setDevice(device);
}
// 推送json 找到设备绑定的主机进行推送
List<NettyDevice> controlMasters = sessionManager.getAllSlaveControl(device.getSerialNumber());
if (!CollectionUtils.isEmpty(controlMasters)) {
controlMasters.forEach(x -> {
DeviceStatusChangeDTO changeDTO = new DeviceStatusChangeDTO();
changeDTO.setDeviceId(data);
changeDTO.setStatus(DeviceStatus.online.getCode());
changeDTO.setEvent(RespondCode.device_status_changed);
ByteBuf buf = ctx.alloc().buffer();
CommandUtils.buildCommand(buf, changeDTO, CommandConstant.interaction);
String content = buf.toString(CharsetUtil.UTF_8);
logger.info("推送下位机登录状态消息:{}", content);
if (x.getChannel() != null && x.getChannel().isActive()) {
x.getChannel().writeAndFlush(buf);
}
});
}
// // 推送json 找到设备绑定的主机进行推送
// List<NettyDevice> controlMasters =
// sessionManager.getAllSlaveControl(device.getSerialNumber());
// if (!CollectionUtils.isEmpty(controlMasters)) {
// controlMasters.forEach(x -> {
// DeviceStatusChangeDTO changeDTO = new DeviceStatusChangeDTO();
// changeDTO.setDeviceId(data);
// changeDTO.setStatus(DeviceStatus.online.getCode());
// changeDTO.setEvent(RespondCode.device_status_changed);
// ByteBuf buf = ctx.alloc().buffer();
// CommandUtils.buildCommand(buf, changeDTO, CommandConstant.interaction);
// String content = buf.toString(CharsetUtil.UTF_8);
// logger.info("推送下位机登录状态消息:{}", content);
// if (x.getChannel() != null && x.getChannel().isActive()) {
// x.getChannel().writeAndFlush(buf);
// }
// });
// }
}
}
} else if (cmdType == CommandConstant.interaction) {
@@ -354,7 +302,7 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
}
}
public void pushFinishData(NettyDevice device, String deviceId) {
public void pushFinishData(NettyDevice device, String deviceId) throws MqttException {
Long taskId = device.finishTask();
// todo 推送信息给前端
DeviceTaskStatusMessageDTO changeDTO = new DeviceTaskStatusMessageDTO();
@@ -363,12 +311,9 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
changeDTO.setType(MesType.device_task_change);
changeDTO.setTaskId(taskId);
try {
GlobalMemory.mqttClientUtil.publish(String.format(MqttTopic.DEVICE_TASK_STATUS_TOPIC, taskId),
JsonUtils.toJsonString(changeDTO));
} catch (MqttException e) {
logger.error("任务完成状态推送失败 task:{},device:{}error:{}", taskId, deviceId, e.getMessage());
}
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
String.format(MqttTopic.DEVICE_TASK_STATUS_TOPIC, taskId),
JsonUtils.toJsonString(changeDTO));
List<NettyDevice> controlMasters = sessionManager
.getAllSlaveControl(deviceId);
if (!CollectionUtils.isEmpty(controlMasters)) {
@@ -382,7 +327,7 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
}
}
public void pushArriveData(NettyDevice device, String deviceId) {
public void pushArriveData(NettyDevice device, String deviceId) throws MqttException {
Long taskId = device.getTask() == null ? null : device.getTask().getId();
// todo 推送信息给前端
DeviceTaskStatusMessageDTO reportDTO = new DeviceTaskStatusMessageDTO();
@@ -390,13 +335,9 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
reportDTO.setTaskId(taskId);
reportDTO.setEntity(device.getCurrentPoint());
reportDTO.setType(MesType.device_task_arrive_point);
try {
GlobalMemory.mqttClientUtil.publish(String.format(MqttTopic.DEVICE_TASK_ARRIVE_TOPIC, taskId),
JsonUtils.toJsonString(reportDTO));
} catch (MqttException e) {
logger.error("任务点位到达推送失败 task:{},device:{}error:{}", taskId, deviceId, e.getMessage());
}
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
String.format(MqttTopic.DEVICE_TASK_ARRIVE_TOPIC, taskId),
JsonUtils.toJsonString(reportDTO));
List<NettyDevice> controlMasters = sessionManager
.getAllSlaveControl(deviceId);
@@ -428,17 +369,20 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
logger.info("12指令切换权限,c:{}", c.getConnectorId());
if (!c.getConnectorId().equals(masterId)) {
// c.setCurrentSlaveId(null);
c.removeConnectedDevice(slaveDevice);
// c.removeConnectedDevice(slaveDevice);
c.setConnectorStatus(ConnectorStatus.RECEIVER);
sysUserClientService.updateAliveStatus(c.getConnectorId(), slaveId, 2);
sessionManager.unLock(c.getConnectorId());
// sysUserClientService.updateAliveStatus(c.getConnectorId(), slaveId, 2);
}
});
}
logger.info("12指令解析,切换权限,slaveId:{},masterId :{}", slaveId, masterId);
sessionManager.lock(slaveId, masterId);
masterDevice.setCurrentSlaveId(slaveId);
masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
masterDevice.addConnectedDevice(slaveDevice);
sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(), slaveId, 1);
// sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(),
// slaveId, 1);
} else {
logger.info("权限切换请求被拒绝");
}
@@ -469,69 +413,122 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
}
}
// /**
// * 找到当前设备控制的主机 转发控制请求
// */
// private void handlePermissionRequest(DeviceRequestDTO requestDTO, byte[]
// bytes, ChannelHandlerContext ctx) {
// logger.info("转发控制请求 :{}", JsonUtils.toJsonString(requestDTO));
// String slaveId = requestDTO.getDeviceId();
// List<NettyDevice> controlList = sessionManager.getSlaveControl(slaveId);
// String masterId = requestDTO.getUserId() + ":" + requestDTO.getPlatform();
// logger.info("转发控制请求 controlList :{}", JsonUtils.toJsonString(controlList));
// if (!CollectionUtils.isEmpty(controlList)) {
// // 如果当前设备有控制
// logger.info("转发控制请求 发送报文");
// NettyDevice controlDevice = controlList.get(0);
// if (controlDevice != null && controlDevice.getChannel() != null &&
// controlDevice.getChannel().isActive()) {
// controlDevice.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
// logger.info("转发控制请求 发送报文结束 deviceId :{}", controlDevice.getConnectorId());
// controlRequestMap.put(masterId, slaveId);
// logger.info("转发控制请求 controlRequestMap:{}",
// JsonUtils.toJsonString(controlRequestMap));
// }
//
// } else {
// // 判断直接给权限 还是 判断是否有该账号其他端的的控制有权限
// NettyDevice activeDevice =
// sessionManager.getUserControl(requestDTO.getUserId());
// boolean sendMes = true;
// if (activeDevice == null) {
// sendMes = false;
// } else {
// if (activeDevice.getConnectorId().equals(masterId)) {
// sendMes = false;
// }
// }
// if (sendMes) {
// if (activeDevice.getChannel() != null &&
// activeDevice.getChannel().isActive()) {
// activeDevice.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
// logger.info("转发控制请求2 发送报文结束 deviceId :{}", activeDevice.getConnectorId());
// controlRequestMap.put(masterId, slaveId);
// logger.info("转发控制请求2 controlRequestMap:{}",
// JsonUtils.toJsonString(controlRequestMap));
// }
// } else {
// NettyDevice masterDevice = sessionManager.getDevice(masterId);
// if (masterDevice != null) {
// NettyDevice slaveDevice = sessionManager.getDevice(slaveId);
// masterDevice.setCurrentSlaveId(slaveId);
// masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
// masterDevice.addConnectedDevice(slaveDevice);
//
// sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(),
// slaveId, 1);
// if (masterDevice.getChannel() != null &&
// masterDevice.getChannel().isActive()) {
// DeviceRequestDTO reDto = new DeviceRequestDTO();
// reDto.setPlatform(requestDTO.getPlatform());
// DeviceRespondDTO respond = new DeviceRespondDTO();
// respond.setSwitchResult(true);
// respond.setHolder("you");
// respond.setDeviceId(slaveId);
// reDto.setRespond(respond);
// ByteBuf buf = ctx.alloc().buffer();
// CommandUtils.buildCommand(buf, reDto, CommandConstant.interaction);
// String content = buf.toString(CharsetUtil.UTF_8);
// masterDevice.getChannel().writeAndFlush(buf);
// logger.info("转发控制请求设置当前控制2 发送报文:{} deviceId :{}", content, masterId);
// }
// } else {
//
// }
// }
// }
// }
/**
* 找到当前设备控制的主机 转发控制请求
*/
private void handlePermissionRequest(DeviceRequestDTO requestDTO, byte[] bytes, ChannelHandlerContext ctx) {
logger.info("转发控制请求 :{}", JsonUtils.toJsonString(requestDTO));
String slaveId = requestDTO.getDeviceId();
List<NettyDevice> controlList = sessionManager.getSlaveControl(slaveId);
String masterId = requestDTO.getUserId() + ":" + requestDTO.getPlatform();
logger.info("转发控制请求 controlList :{}", JsonUtils.toJsonString(controlList));
if (!CollectionUtils.isEmpty(controlList)) {
String token = requestDTO.getToken();
String master = sessionManager.getMaster(slaveId);
if (!StringUtils.isEmpty(master)) {
// 如果当前设备有控制
logger.info("转发控制请求 发送报文");
NettyDevice controlDevice = controlList.get(0);
NettyDevice controlDevice = sessionManager.getDevice(master);
if (controlDevice != null && controlDevice.getChannel() != null && controlDevice.getChannel().isActive()) {
controlDevice.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
logger.info("转发控制请求 发送报文结束 deviceId :{}", controlDevice.getConnectorId());
controlRequestMap.put(masterId, slaveId);
controlRequestMap.put(token, slaveId);
logger.info("转发控制请求 controlRequestMap:{}", JsonUtils.toJsonString(controlRequestMap));
}
} else {
// 判断直接给权限 还是 判断是否有该账号其他端的的控制有权限
NettyDevice activeDevice = sessionManager.getUserControl(requestDTO.getUserId());
boolean sendMes = true;
if (activeDevice == null) {
sendMes = false;
} else {
if (activeDevice.getConnectorId().equals(masterId)) {
sendMes = false;
}
}
if (sendMes) {
if (activeDevice.getChannel() != null && activeDevice.getChannel().isActive()) {
activeDevice.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
logger.info("转发控制请求2 发送报文结束 deviceId :{}", activeDevice.getConnectorId());
controlRequestMap.put(masterId, slaveId);
logger.info("转发控制请求2 controlRequestMap:{}", JsonUtils.toJsonString(controlRequestMap));
}
} else {
NettyDevice masterDevice = sessionManager.getDevice(masterId);
if (masterDevice != null) {
NettyDevice slaveDevice = sessionManager.getDevice(slaveId);
masterDevice.setCurrentSlaveId(slaveId);
masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
masterDevice.addConnectedDevice(slaveDevice);
sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(), slaveId, 1);
if (masterDevice.getChannel() != null && masterDevice.getChannel().isActive()) {
DeviceRequestDTO reDto = new DeviceRequestDTO();
reDto.setPlatform(requestDTO.getPlatform());
DeviceRespondDTO respond = new DeviceRespondDTO();
respond.setSwitchResult(true);
respond.setHolder("you");
respond.setDeviceId(slaveId);
reDto.setRespond(respond);
ByteBuf buf = ctx.alloc().buffer();
CommandUtils.buildCommand(buf, reDto, CommandConstant.interaction);
String content = buf.toString(CharsetUtil.UTF_8);
masterDevice.getChannel().writeAndFlush(buf);
logger.info("转发控制请求设置当前控制2 发送报文:{} deviceId :{}", content, masterId);
}
} else {
NettyDevice masterDevice = sessionManager.getDevice(token);
if (masterDevice != null) {
NettyDevice slaveDevice = sessionManager.getDevice(slaveId);
masterDevice.setCurrentSlaveId(slaveId);
masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
masterDevice.addConnectedDevice(slaveDevice);
sessionManager.lock(slaveId, token);
if (masterDevice.getChannel() != null && masterDevice.getChannel().isActive()) {
DeviceRequestDTO reDto = new DeviceRequestDTO();
reDto.setPlatform(requestDTO.getPlatform());
DeviceRespondDTO respond = new DeviceRespondDTO();
respond.setSwitchResult(true);
respond.setHolder("you");
respond.setDeviceId(slaveId);
reDto.setRespond(respond);
ByteBuf buf = ctx.alloc().buffer();
CommandUtils.buildCommand(buf, reDto, CommandConstant.interaction);
String content = buf.toString(CharsetUtil.UTF_8);
masterDevice.getChannel().writeAndFlush(buf);
logger.info("转发控制请求设置当前控制2 发送报文:{} deviceId :{}", content, token);
}
}
}
@@ -543,7 +540,7 @@ public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
}
private void handleMasterConnect(ChannelHandlerContext ctx, String masterId, String slaveId,
ConnectorStatus status) {
ConnectorStatus status) {
ctx.channel().attr(Constant.ATT_DEVICE_ID).set(masterId);
sessionManager.registerDevice(masterId, ctx.channel(), ConnectorType.MASTER, status, slaveId);
// sessionManager.bindConnector(masterId, slaveId);

View File

@@ -31,6 +31,8 @@ public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(HeartbeatHandler.class);
private static final long DEVICE_REGISTER_TIMEOUT_MS = 10_000; // 10秒注册超时
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 连接建立后启动定时任务,主动发心跳
@@ -39,6 +41,7 @@ public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
}
private void scheduleHeartbeat(ChannelHandlerContext ctx) {
final long startTime = System.currentTimeMillis();
ctx.executor().scheduleAtFixedRate(() -> {
try {
if (ctx.channel().isActive()) {
@@ -47,6 +50,12 @@ public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
ByteBuf heartbeat = Unpooled.wrappedBuffer(HEARTBEAT_PACKET);
ctx.writeAndFlush(heartbeat);
logger.debug("发送心跳包给客户端:{},deviceId:{}", ctx.channel().remoteAddress(), deviceId);
} else {
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed > DEVICE_REGISTER_TIMEOUT_MS) {
logger.warn("设备超过{}ms未注册,断开连接:{}", DEVICE_REGISTER_TIMEOUT_MS, ctx.channel().remoteAddress());
ctx.close();
}
}
}
} catch (Exception e) {
@@ -55,6 +64,24 @@ public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
}, 0, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
}
// private void scheduleHeartbeat(ChannelHandlerContext ctx) {
// ctx.executor().scheduleAtFixedRate(() -> {
// try {
// if (ctx.channel().isActive()) {
// String deviceId = ctx.channel().attr(Constant.ATT_DEVICE_ID).get();
// if (!StringUtils.isEmpty(deviceId)) {
// ByteBuf heartbeat = Unpooled.wrappedBuffer(HEARTBEAT_PACKET);
// ctx.writeAndFlush(heartbeat);
// logger.debug("发送心跳包给客户端:{},deviceId:{}", ctx.channel().remoteAddress(), deviceId);
// }
// }
// } catch (Exception e) {
// logger.error("heartbeat error", e);
// }
// }, 0, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
// }
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {

View File

@@ -1,6 +1,6 @@
package com.maibu.scheduled;
import com.maibu.common.NettyCacheKey;
import com.maibu.constant.NettyCacheKey;
import com.maibu.core.business.DevicePlan;
import com.maibu.core.business.DeviceRunParam;
import com.maibu.core.business.DeviceStatusRecordDTO;

View File

@@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.PreDestroy;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -44,13 +45,12 @@ public class DevicePlanTaskMonitorService {
@Autowired
private WorkRecordMapper workRecordMapper;
@Autowired
private ScheduledExecutorService executor;
/**
* 计划迟的任务生成机器执行任务
*/
public void generatePlanTask() throws InterruptedException {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(() -> {
try {
Map<String, List<DevicePlan>> planMap = SiteMemory.devicePlanMap;
@@ -204,7 +204,6 @@ public class DevicePlanTaskMonitorService {
public void doLoop() throws InterruptedException {
//prepare 进执行execute的逻辑
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(() -> {
try {
List<SiteMemory> list = GlobalMemory.getAllSiteMemory();
@@ -215,11 +214,11 @@ public class DevicePlanTaskMonitorService {
planMap.keySet().forEach(key -> {
NettyDevice device = deviceSessionManager.getDevice(key);
//判断是否能进execute
if (device != null && device.getCurrentTaskId() == null && device.getOnlineStatus() != null && device.getOnlineStatus() == 1) {
if (device != null && device.getTask() == null && device.getOnlineStatus() != null && device.getOnlineStatus() == 1) {
List<DevicePlanTask> tasks = planMap.get(key);
if (!CollectionUtils.isEmpty(tasks)) {
tasks.removeIf(Objects::isNull);
tasks.sort(
tasks.sort(
Comparator.comparing(
DevicePlanTask::getStartTime,
Comparator.nullsLast(Comparator.naturalOrder())
@@ -230,9 +229,9 @@ public class DevicePlanTaskMonitorService {
String deviceId = devicePlanTask.getDeviceId();
if (!StringUtils.isEmpty(deviceId) && deviceId.equals(key)) {
devicePlanTask.setTaskStaus(DeviceTaskStaus.EXECUTING);
siteMemory.addDevicePlanExecute(devicePlanTask);
device.setCurrentTaskId(devicePlanTask.getId());
device.setTask(devicePlanTask);
siteMemory.addDevicePlanExecute(devicePlanTask);
siteMemory.removeDevicePlanPrepare(devicePlanTask);
}
}
@@ -250,7 +249,6 @@ public class DevicePlanTaskMonitorService {
public void doExecute() {
//prepare 进执行execute的逻辑
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(() -> {
try {
List<SiteMemory> list = GlobalMemory.getAllSiteMemory();
@@ -270,5 +268,4 @@ public class DevicePlanTaskMonitorService {
}
}, 0, 1000, TimeUnit.MILLISECONDS);
}
}

View File

@@ -11,6 +11,7 @@ import java.util.stream.Collectors;
import com.maibu.memory.DeviceSessionManager;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -23,6 +24,7 @@ import com.maibu.core.business.DeviceTaskPlanRule;
import com.maibu.core.business.WorkRecord;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.dto.DeviceTaskCommandDTO;
import com.maibu.core.domain.entity.SysRegion;
import com.maibu.core.domain.entity.SysSite;
import com.maibu.core.domain.model.LoginUser;
import com.maibu.core.enums.DeviceTaskStaus;
@@ -33,6 +35,7 @@ import com.maibu.dto.DeviceWorkStatisticsDTO;
import com.maibu.mapper.DeviceMapper;
import com.maibu.mapper.DevicePlanMapper;
import com.maibu.mapper.DevicePlanTaskMapper;
import com.maibu.mapper.SysRegionMapper;
import com.maibu.mapper.SysSiteMapper;
import com.maibu.mapper.WorkRecordMapper;
import com.maibu.memory.GlobalMemory;
@@ -62,6 +65,9 @@ public class DeviceTaskService {
@Autowired
private SysSiteMapper sysSiteMapper;
@Autowired
private SysRegionMapper sysRegionMapper;
public int insertOrUpdate(DevicePlan devicePlan, LoginUser loginUser) {
Long id = devicePlan.getId();
SiteMemory siteMemory = GlobalMemory.getSiteMemory(devicePlan.getOrgId(), devicePlan.getSiteId());
@@ -476,7 +482,36 @@ public class DeviceTaskService {
return task.getId();
}
public boolean cancelTask(DeviceTaskCommandDTO dto, String username) {
public Long createRegionDeviceTask(DeviceTaskCommandDTO dto, String username) {
LambdaQueryWrapper<SysRegion> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysRegion::getId, dto.getRegionId());
SysRegion region = sysRegionMapper.selectOne(queryWrapper);
if (region == null || region.getBindRoute() == null) {
throw new RuntimeException("区域未绑定线路!");
}
DevicePlanTask task = new DevicePlanTask();
task.setRouteId(region.getBindRoute());
task.setOrgId(dto.getOrgId());
task.setSiteId(dto.getSiteId());
task.setTaskStaus(DeviceTaskStaus.NEW);
task.setDeviceId(dto.getDeviceId());
task.setCreateBy(username);
task.setCreateTime(LocalDateTime.now());
task.setStartTime(dto.getStartTime());
devicePlanTaskMapper.saveOrUpdate(task);
if (dto.getOrgId() == null) {
SysSite site = sysSiteMapper.selectById(dto.getSiteId());
if (site != null) {
dto.setOrgId(site.getOrgId());
}
}
SiteMemory siteMemory = GlobalMemory.getSiteMemory(task.getOrgId(), task.getSiteId());
siteMemory.addDevicePlanPrepare(task);
return task.getId();
}
public boolean cancelTask(DeviceTaskCommandDTO dto) throws MqttException {
String deviceId = dto.getDeviceId();
DevicePlanTask devicePlanTask = null;
if (StringUtils.isEmpty(dto.getDeviceId())) {
@@ -488,7 +523,11 @@ public class DeviceTaskService {
}
NettyDevice device = deviceSessionManager.getDevice(deviceId);
if (device != null && device.getTask() != null) {
return device.cancelTask();
if (dto.isForceCancel()) {
return device.canceledTask();
} else {
return device.cancelingTask();
}
} else {
if (devicePlanTask != null) {
devicePlanTask.setTaskStaus(DeviceTaskStaus.CANCELED);

View File

@@ -2,6 +2,7 @@ package com.maibu.service;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
@@ -9,15 +10,18 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.core.business.dto.DeviceMapPushDTO;
import com.maibu.core.business.dto.WebMapMessageDTO;
import com.maibu.core.enums.*;
import com.maibu.utils.json.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.checkerframework.checker.units.qual.s;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
@@ -25,19 +29,24 @@ import org.springframework.util.CollectionUtils;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.maibu.common.NettyCacheKey;
import com.maibu.constant.Constant;
import com.maibu.constant.NettyCacheKey;
import com.maibu.core.business.Device;
import com.maibu.core.business.DeviceRunStatistics;
import com.maibu.core.business.DeviceRunningStatusHistory;
import com.maibu.core.business.DeviceStatusRecordDTO;
import com.maibu.core.business.ErrorIdentificationStandard;
import com.maibu.core.business.alarm_center.AlarmMessage;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.inter.WebsocketMesDispather;
import com.maibu.core.enums.CompareEnum;
import com.maibu.core.enums.ErrorSource;
import com.maibu.core.enums.RespondCode;
import com.maibu.core.host.HeartBeatDTO;
import com.maibu.core.host.LocationMessage;
import com.maibu.core.redis.RedisCache;
import com.maibu.dto.DeviceErrorPushDTO;
import com.maibu.mapper.DeviceRunStatisticsMapper;
import com.maibu.mapper.ErrorIdentificationStandardMapper;
import com.maibu.mapper.HostLocationMapper;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.memory.GlobalMemory;
import com.maibu.memory.SiteMemory;
import com.maibu.mqtt.MqttTopic;
@@ -69,13 +78,26 @@ public class DeviceThreadService {
@Autowired
private AlertPushService alertPushService;
@Autowired
private IDeviceService deviceService;
@Value("${mqtt.client-id}")
private String mqttClientId;
@Autowired
private ScheduledExecutorService executor;
@Autowired
private HostLocationMapper hostLocationMapper;
@Autowired
private DeviceRunStatisticsMapper deviceRunStatisticsMapper;
public void deviceErrorMonitor() throws InterruptedException, IOException {
initStandard();
// TODO fix 后续是否分多个线程 一个site一个线程
// TODO fix 后续是否分多个线程 一个site一个线程
// 有还需要更新接受故障方式。
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(() -> {
try {
String key = NettyCacheKey.deviceRunningStatusKey;
@@ -92,7 +114,7 @@ public class DeviceThreadService {
String[] parts = k.split(":");
String deviceId = parts[1];
NettyDevice slaveDevice = sessionManager.getDevice(deviceId);
if (slaveDevice != null && slaveDevice.isOnline()) {
if (slaveDevice != null && slaveDevice.getOnlineStatus() == 1) {
Long siteId = slaveDevice.getDevice().getSiteId();
Long orgId = slaveDevice.getDevice().getOrgId();
log.debug("compare siteId:{},orgId:{}", siteId, orgId);
@@ -184,7 +206,10 @@ public class DeviceThreadService {
// }
// }
try {
GlobalMemory.mqttClientUtil.publish(String.format(MqttTopic.DEVICE_ERROR_PUSH_TOPIC, deviceId), dto);
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
String.format(MqttTopic.DEVICE_ERROR_PUSH_TOPIC, deviceId), dto);
// GlobalMemory.mqttClientUtil.publish(String.format(MqttTopic.DEVICE_ERROR_PUSH_TOPIC,
// deviceId), dto);
List<NettyDevice> controlMasters = sessionManager.getAllSlaveControl(deviceId);
if (!CollectionUtils.isEmpty(controlMasters)) {
controlMasters.forEach(x -> {
@@ -192,7 +217,7 @@ public class DeviceThreadService {
log.info("推送错误信息 device:{},content:{}", deviceId, dto);
});
}
} catch (MqttException e) {
} catch (Exception e) {
log.error("推送错误信息失败 device:{}error:{}", deviceId, e.getMessage());
}
}
@@ -207,7 +232,8 @@ public class DeviceThreadService {
String compareValues = standard.getCompareValues();
CompareEnum compareType = standard.getCompareType();
String targetValue = knownClassFieldFinding(fieldName, history);
log.debug("compare2 compareType:{} compareValues:{},targetValue:{}", compareType, compareValues, targetValue);
log.debug("compare2 compareType:{} compareValues:{},targetValue:{}", compareType, compareValues,
targetValue);
if (!StringUtils.isEmpty(compareValues) && !StringUtils.isEmpty(targetValue)) {
boolean result = CompareUtils.compare(compareType, targetValue, compareValues);
log.debug("compare3 result:{}", result);
@@ -248,4 +274,155 @@ public class DeviceThreadService {
return null;
}
/**
* 监控心跳
*/
public void heartBeatMonitor() {
executor.scheduleWithFixedDelay(() -> {
try {
Long nowTime = System.currentTimeMillis();
List<HeartBeatDTO> heartBeatDTOS = GlobalMemory.customMqttDeviceMonitor.getAllHeartBeat();
if (!CollectionUtils.isEmpty(heartBeatDTOS)) {
heartBeatDTOS.forEach(x -> {
if (x == null) {
return;
}
String deviceId = x.getSn();
Long lastActiveTime = x.getSysTimestamp();
NettyDevice nettyDevice = sessionManager.getDevice(x.getSn());
if (lastActiveTime == null) {
log.error("设备心跳时间为空,deviceId={}", deviceId);
return;
}
if (nowTime - lastActiveTime >= Constant.hostOfflineInterval) {
// 超时了判断离线
if (nettyDevice != null) {
nettyDevice.status(DeviceStatus.offline.getType());
// todo 推送设备离线
String key = NettyCacheKey.latestPositonKey + deviceId;
// 保存一下最新一次的数据到 数据库
LocationMessage locationMessage = redisCache.getCacheObject(key);
if (locationMessage != null) {
LambdaQueryWrapper<LocationMessage> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(LocationMessage::getDeviceId, deviceId);
LocationMessage existing = hostLocationMapper.selectOne(queryWrapper);
if (existing != null) {
locationMessage.setId(existing.getId());
}
hostLocationMapper.saveOrUpdate(locationMessage);
}
Device device = deviceService.selectDeviceBySerialNumber(deviceId);
if (device != null) {
device.setOnlineStatus(DeviceStatus.offline.getType());
device.setUpdateTime(LocalDateTime.now());
deviceService.updateDeviceBySelf(device);
}
Long orgId = nettyDevice.getDevice().getOrgId();
Long siteId = nettyDevice.getDevice().getSiteId();
SiteMemory siteMemory = GlobalMemory.getSiteMemory(orgId, siteId);
if (siteMemory != null) {
DeviceRunStatistics deviceRunStatistics = siteMemory.getRunStatistics(deviceId);
if (deviceRunStatistics != null) {
LambdaQueryWrapper<DeviceRunStatistics> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeviceRunStatistics::getDeviceId, deviceId);
DeviceRunStatistics existing = deviceRunStatisticsMapper
.selectOne(queryWrapper);
if (existing != null) {
deviceRunStatistics.setId(existing.getId());
deviceRunStatistics.setDistance(
existing.getDistance() + deviceRunStatistics.getDistance());
deviceRunStatistics.setWorkArea(
existing.getWorkArea() + deviceRunStatistics.getWorkArea());
long time = System.currentTimeMillis() - nettyDevice.getLatestLoginTime();
deviceRunStatistics.setTime(existing.getTime() + time / (1000 * 60)); // 累加时长,单位为分钟
} else {
long time = System.currentTimeMillis() - nettyDevice.getLatestLoginTime();
deviceRunStatistics.setTime(time / (1000 * 60)); // 累加时长,单位为分钟
}
deviceRunStatisticsMapper.saveOrUpdate(deviceRunStatistics);
}
}
sessionManager.removeDevice(deviceId);
// TODO 统计总里程、总作业面积
}
} else {
Device device = deviceService.selectDeviceBySerialNumber(deviceId);
if (device == null) {
device = new Device();
device.setStatus(3);
device.setOnlineStatus(DeviceStatus.online.getType());
device.setSerialNumber(deviceId);
device.setDeviceName(deviceId);
device.setProductId(-1L);
device.setTenantId(-1L);
device.setFirmwareVersion(BigDecimal.valueOf(1.0));
device.setProductName("割草机产品MC700");
device.setCreateTime(LocalDateTime.now());
device.setUpdateTime(LocalDateTime.now());
device.setHasHost(true);
deviceService.insertDeviceBySelf(device);
} else {
device.setStatus(3);
device.setOnlineStatus(DeviceStatus.online.getType());
device.setUpdateTime(LocalDateTime.now());
device.setHasHost(true);
deviceService.updateDeviceBySelf(device);
}
if (nettyDevice == null) {
sessionManager.registerSlaveDevice(deviceId, ConnectorStatus.ACTIVE, null);
NettyDevice slaveDevice = sessionManager.getDevice(deviceId);
if (slaveDevice != null) {
slaveDevice.setOnlineStatus(1);
slaveDevice.setDevice(device);
}
} else {
// todo 推送设备上线
nettyDevice.status(1);
nettyDevice.setDevice(device);
}
}
});
}
} catch (Exception e) {
log.error("执行监测心跳线程错误", e);
}
}, 0, 500, TimeUnit.MILLISECONDS);
}
/**
* 监控心跳
*/
public void mapMessagePush() {
executor.scheduleWithFixedDelay(() -> {
try {
List<SiteMemory> list = GlobalMemory.getAllSiteMemory();
if (!CollectionUtils.isEmpty(list)) {
list.forEach(x -> {
Map<String, DeviceMapPushDTO> map = x.getAllDeviceMapPush();
if (!CollectionUtils.isEmpty(map)) {
List<DeviceMapPushDTO> pushList = new ArrayList<>(map.values());
WebMapMessageDTO dto = new WebMapMessageDTO();
dto.setData(pushList);
dto.setType(MesType.mapPush);
try {
websocketMesDispather.mapMessageDispather(x.getSiteId(), JsonUtils.toJsonString(pushList));
} catch (Exception e) {
log.error("实时地图数据websocket推送失败: siteId={}, error={}", x.getSiteId(),
e.getMessage(), e);
}
}
});
}
} catch (Exception e) {
log.error("执行监测心跳线程错误:{}", e.getMessage());
}
}, 0, 100, TimeUnit.MILLISECONDS);
}
}

View File

@@ -7,10 +7,12 @@ import com.maibu.core.domain.AjaxResult;
import com.maibu.core.domain.entity.UserClient;
import com.maibu.core.domain.model.LoginUser;
import com.maibu.core.enums.ConnectorStatus;
import com.maibu.exception.ServiceException;
import com.maibu.mapper.DeviceMapper;
import com.maibu.mapper.SysUserClientMapper;
import com.maibu.memory.DeviceSessionManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -20,6 +22,7 @@ import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
@Slf4j
@@ -155,62 +158,111 @@ public class TransferDeviceService {
}
}
public synchronized boolean switchDevice(DeviceBindDTO bindDTO, LoginUser user) {
// public synchronized boolean switchDevice(DeviceBindDTO bindDTO, LoginUser user) {
// try {
// String slaveId = bindDTO.getDeviceId();
// String platform = bindDTO.getPlatform();
// String userName = user.getUsername();
// String masterId = userName + ":" + platform;
// //todo 判断是否有权限 正在控制才能切
// //todo 离线没有控制权的怎么切
// log.info("switchDevice:{}", masterId);
// NettyDevice deviceMaster = sessionManager.getDevice(masterId);
// log.info("deviceMaster:{}", deviceMaster);
// if (deviceMaster != null) {
// log.info("ConnectorStatus:{}", deviceMaster.getConnectorStatus());
// if (ConnectorStatus.RECEIVER.equals(deviceMaster.getConnectorStatus())) {
// //当前账号没有控制则可以切换为控制?
// NettyDevice activeDevice = sessionManager.getUserControl(userName);
// deviceMaster.getConnectedConnectors().clear();
// if (activeDevice == null) {
// log.info("bindConnector ACTIVE :{}", masterId);
// sessionManager.bindConnector(masterId, slaveId);
// deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
// deviceMaster.setCurrentSlaveId(slaveId);
// sysUserClientService.updateAliveStatus(masterId, slaveId, 1);
// } else {
// log.info("bindConnector RECEIVER :{}", masterId);
// sessionManager.bindConnector(masterId, slaveId);
// deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
// deviceMaster.setCurrentSlaveId(slaveId);
// sysUserClientService.updateAliveStatus(masterId, slaveId, 2);
// }
// } else {
// //从关联其他设备切换到 当前禁言 假如有控制且不是当前更新为2
// List<NettyDevice> controls = sessionManager.getSlaveControl(slaveId);
// if (CollectionUtils.isEmpty(controls)) {
// deviceMaster.getConnectedConnectors().clear();
// log.info("bindConnector 1:{}", masterId);
// sessionManager.bindConnector(masterId, slaveId);
// deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
// deviceMaster.setCurrentSlaveId(slaveId);
// sysUserClientService.updateAliveStatus(masterId, slaveId, 1);
// } else {
// NettyDevice controlDevice = controls.get(0);
// if (!masterId.equals(controlDevice.getConnectorId())) {
// //当前的设备有控制 的主机
// deviceMaster.getConnectedConnectors().clear();
// log.info("bindConnector RECEIVER 2:{}", masterId);
// sessionManager.bindConnector(masterId, slaveId);
// deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
// deviceMaster.setCurrentSlaveId(slaveId);
// sysUserClientService.updateAliveStatus(masterId, slaveId, 2);
// }
// //当前 切当前不做操作
// }
// }
// return true;
// }
// } catch (Exception e) {
// log.error("切换设备失败: {}", e.getMessage(), e);
// }
// return false;
// }
public synchronized boolean switchDevice(DeviceBindDTO bindDTO) {
try {
String slaveId = bindDTO.getDeviceId();
String platform = bindDTO.getPlatform();
String userName = user.getUsername();
String masterId = userName + ":" + platform;
//todo 判断是否有权限 正在控制才能切
//todo 离线没有控制权的怎么切
log.info("switchDevice:{}", masterId);
NettyDevice deviceMaster = sessionManager.getDevice(masterId);
log.info("deviceMaster:{}", deviceMaster);
if (deviceMaster != null) {
log.info("ConnectorStatus:{}", deviceMaster.getConnectorStatus());
if (ConnectorStatus.RECEIVER.equals(deviceMaster.getConnectorStatus())) {
//当前账号没有控制则可以切换为控制?
NettyDevice activeDevice = sessionManager.getUserControl(userName);
deviceMaster.getConnectedConnectors().clear();
if (activeDevice == null) {
log.info("bindConnector ACTIVE :{}", masterId);
sessionManager.bindConnector(masterId, slaveId);
deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
deviceMaster.setCurrentSlaveId(slaveId);
sysUserClientService.updateAliveStatus(masterId, slaveId, 1);
} else {
log.info("bindConnector RECEIVER :{}", masterId);
sessionManager.bindConnector(masterId, slaveId);
deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
deviceMaster.setCurrentSlaveId(slaveId);
sysUserClientService.updateAliveStatus(masterId, slaveId, 2);
}
} else {
//从关联其他设备切换到 当前禁言 假如有控制且不是当前更新为2
List<NettyDevice> controls = sessionManager.getSlaveControl(slaveId);
if (CollectionUtils.isEmpty(controls)) {
deviceMaster.getConnectedConnectors().clear();
log.info("bindConnector 1:{}", masterId);
sessionManager.bindConnector(masterId, slaveId);
deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
deviceMaster.setCurrentSlaveId(slaveId);
sysUserClientService.updateAliveStatus(masterId, slaveId, 1);
} else {
NettyDevice controlDevice = controls.get(0);
if (!masterId.equals(controlDevice.getConnectorId())) {
//当前的设备有控制 的主机
deviceMaster.getConnectedConnectors().clear();
log.info("bindConnector RECEIVER 2:{}", masterId);
sessionManager.bindConnector(masterId, slaveId);
deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
deviceMaster.setCurrentSlaveId(slaveId);
sysUserClientService.updateAliveStatus(masterId, slaveId, 2);
}
//当前 切当前不做操作
}
}
return true;
// String platform = bindDTO.getPlatform();
// String userName = user.getUsername();
// String masterId = userName + ":" + platform;
String token = bindDTO.getToken();
log.info("switchDevice slaveId:{},token:{}", slaveId, token);
NettyDevice deviceMaster = sessionManager.getDevice(token);
log.info("switchDevice deviceMaster:{}", deviceMaster);
if (deviceMaster == null) {
throw new ServiceException("功能码当前账号未发送登录请求,不在线");
}
List<NettyDevice> controls = sessionManager.getAllSlaveControl(slaveId);
if (CollectionUtils.isEmpty(controls)) {
deviceMaster.getConnectedConnectors().clear();
sessionManager.bindConnector(token, slaveId);
deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
deviceMaster.setCurrentSlaveId(slaveId);
sessionManager.lock(slaveId, token);
log.info("switchDevice bindConnector slaveId:{},token:{}", slaveId, token);
} else {
List<String> connectorIds = controls.stream().map(NettyDevice::getConnectorId).collect(Collectors.toList());
if (!connectorIds.contains(token)) {
//当前的设备有控制 的主机
deviceMaster.getConnectedConnectors().clear();
sessionManager.bindConnector(token, slaveId);
deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
deviceMaster.setCurrentSlaveId(slaveId);
log.info("switchDevice bindConnector RECEIVER :{}", token);
} else {
deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
deviceMaster.setCurrentSlaveId(slaveId);
sessionManager.lock(slaveId, token);
}
//当前 切当前不做操作
}
return true;
} catch (Exception e) {
log.error("切换设备失败: {}", e.getMessage(), e);
}
@@ -218,53 +270,18 @@ public class TransferDeviceService {
}
public DeviceControlDTO remoteControl(DeviceBindDTO bindDTO, LoginUser user) {
public DeviceControlDTO remoteControl(DeviceBindDTO bindDTO) {
try {
String slaveId = bindDTO.getDeviceId();
String platform = bindDTO.getPlatform();
String userName = user.getUsername();
String masterId = userName + ":" + platform;
String token = bindDTO.getToken();
String masterToken = sessionManager.getMaster(bindDTO.getDeviceId());
DeviceControlDTO dto = new DeviceControlDTO();
NettyDevice deviceMaster = sessionManager.getDevice(masterId);
log.info("remoteControl deviceMaster:{}", masterId);
// //TODO 拓展当前控制不是同一个账号
// NettyDevice currentDevice = sessionManager.getCurrentControl(slaveId);
// if(currentDevice != null){
// if(currentDevice.getConnectorId().equals(masterId)){
// dto.setRemoteControl(true);
// dto.setOwner(platform);
// }else{
// dto.setRemoteControl(false);
// dto.setOwner(currentDevice.getConnectorId());
// }
// }else{
// dto.setRemoteControl(false);
// dto.setOwner(null);
// dto.setReason("occupied");
// }
if (deviceMaster != null) {
if (ConnectorStatus.ACTIVE.equals(deviceMaster.getConnectorStatus())) {
dto.setRemoteControl(true);
dto.setOwner(platform);
} else {
dto.setRemoteControl(false);
dto.setReason("occupied");
NettyDevice activeDevice = sessionManager.getUserControl(userName);
if (activeDevice != null) {
String[] l = activeDevice.getConnectorId().split(":");
dto.setOwner(l[1]);
}
}
if (!StringUtils.isEmpty(token) && token.equals(masterToken)) {
dto.setRemoteControl(true);
dto.setOwner("you");
} else {
dto.setRemoteControl(false);
dto.setReason("not exist");
NettyDevice activeDevice = sessionManager.getUserControl(userName);
if (activeDevice != null) {
String[] l = activeDevice.getConnectorId().split(":");
dto.setOwner(l[1]);
}
dto.setReason("occupied");
dto.setOwner(masterToken);
}
return dto;
} catch (Exception e) {
@@ -316,7 +333,7 @@ public class TransferDeviceService {
}
masterDevice.setCurrentSlaveId(slaveId);
masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(), slaveId, 1);
// sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(), slaveId, 1);
dto.setSwitchControl(true);
dto.setHolder(platform);
@@ -327,23 +344,20 @@ public class TransferDeviceService {
}
}
public void releaseControl(DeviceBindDTO bindDTO, LoginUser user) {
public void releaseControl(DeviceBindDTO bindDTO) {
try {
String platform = bindDTO.getPlatform();
String userName = user.getUsername();
String masterId = userName + ":" + platform;
Integer type = bindDTO.getType();
NettyDevice deviceMaster = sessionManager.getDevice(masterId);
if (deviceMaster != null && type!=null) {
if(type == 1){
sysUserClientService.updateAliveStatus(masterId, "", 0);
deviceMaster.setCurrentSlaveId(null);
deviceMaster.getConnectedConnectors().clear();
String masterId = sessionManager.getMaster(bindDTO.getDeviceId());
if (type != null && !StringUtils.isEmpty(masterId)) {
NettyDevice device = sessionManager.getDevice(masterId);
if (type == 1) {
device.setCurrentSlaveId(null);
device.getConnectedConnectors().clear();
} else if (type == 2) {
deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
sysUserClientService.updateAliveStatus(masterId, deviceMaster.getCurrentSlaveId(), 2);
device.setConnectorStatus(ConnectorStatus.RECEIVER);
}
}
sessionManager.unLock(bindDTO.getDeviceId(), bindDTO.getToken(), false);
} catch (Exception e) {
log.error(e.getMessage());
}

View File

@@ -272,6 +272,14 @@ public class DeviceController extends BaseController {
return getDataTable(deviceService.getSiteList(getLoginUser(), device));
}
/**
* 查询设备列表 分配页面
*/
@GetMapping("/getDeviceBySpiltCode")
public AjaxResult getDeviceBySpiltCode(@RequestParam String code) {
return AjaxResult.success(deviceService.getDeviceBySpiltCode(code));
}
/**
* 查询设备列表 分配页面
*/

View File

@@ -4,6 +4,11 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import com.maibu.annotation.Log;
import com.maibu.core.domain.entity.SysUser;
import com.maibu.enums.BusinessType;
import com.maibu.utils.poi.ExcelUtil;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -18,6 +23,7 @@ import com.maibu.core.domain.AjaxResult;
import com.maibu.core.page.TableDataInfo;
import com.maibu.dto.BaseQueryDTO;
import com.maibu.service.impl.DeviceRunParamService;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/iot/deviceRunParam")
@@ -47,5 +53,12 @@ public class DeviceRunParamController extends BaseController {
public AjaxResult delete(@RequestBody List<Long> ids) {
return AjaxResult.success(service.delete(ids));
}
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file) throws Exception {
ExcelUtil<DeviceRunParam> util = new ExcelUtil<DeviceRunParam>(DeviceRunParam.class);
List<DeviceRunParam> paramList = util.importExcel(file.getInputStream());
boolean message = service.importParam(paramList);
return success(message);
}
}

View File

@@ -0,0 +1,248 @@
package com.maibu.controller;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.GZIPInputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.dto.WebRouteStatusMessageDTO;
import com.maibu.core.business.inter.WebsocketMesDispather;
import com.maibu.core.business.path.LatAndLngEntity;
import com.maibu.core.controller.BaseController;
import com.maibu.core.domain.R;
import com.maibu.core.enums.MesType;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.memory.GlobalMemory;
import com.maibu.mqtt.MqttTopic;
import com.maibu.service.impl.HostComputerService;
import com.maibu.utils.json.JsonUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping("/iot/host")
public class HostComputerController extends BaseController {
private static final int BATCH_SIZE = 5000;
@Autowired
private HostComputerService hostComputerService;
@Autowired
private WebsocketMesDispather websocketMesDispather;
@Autowired
private DeviceSessionManager deviceSessionManager;
@Value("${mqtt.client-id}")
private String mqttClientId;
/**
* 上传 csv.gz 轨迹文件并解析为 LatAndLngEntity 列表(分批处理,每批 BATCH_SIZE 条)
*
* <p>
* CSV 格式:第 1 列=cell_index,第 2 列=x(lat),第 3 列=y(lng)
* </p>
*
* @param file csv.gz 压缩文件
* @param taskId 任务ID
* @return 解析后的完整轨迹数据
*/
@PostMapping(value = "/upload-path-csv", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R uploadCsv(
@RequestPart("file") MultipartFile file,
@RequestParam("taskId") Long taskId) {
if (file == null || file.isEmpty()) {
return R.fail("文件不能为空");
}
if (taskId == null) {
return R.fail("taskId 不能为空");
}
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".csv.gz")) {
return R.fail("只支持 .csv.gz 格式文件");
}
log.info("收到 csv.gz 文件: taskId={}, fileName={}, size={}", taskId, originalFilename, file.getSize());
List<LatAndLngEntity> allPath = new ArrayList<>();
List<LatAndLngEntity> batch = new ArrayList<>(BATCH_SIZE);
int batchIndex = 0;
try (GZIPInputStream gzipInputStream = new GZIPInputStream(file.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream))) {
String line;
int lineNum = 0;
while ((line = reader.readLine()) != null) {
lineNum++;
if (line.trim().isEmpty()) {
continue;
}
String[] values = line.split(",");
if (lineNum < 4) {
log.info("CSV 表头: {}", Arrays.toString(values));
continue;
}
if (values.length < 3) {
log.warn("第 {} 行列数不足3列,跳过: {}", lineNum, line);
continue;
}
LatAndLngEntity entity = parseLatLng(values);
if (entity != null) {
batch.add(entity);
}
if (batch.size() >= BATCH_SIZE) {
batchIndex++;
processBatch(taskId, batch, batchIndex);
allPath.addAll(batch);
batch.clear();
log.info("csv 解析进度: taskId={}, 第 {} 批({}条), 累计 {} 条",
taskId, batchIndex, BATCH_SIZE, allPath.size());
}
}
if (!batch.isEmpty()) {
batchIndex++;
processBatch(taskId, batch, batchIndex);
allPath.addAll(batch);
batch.clear();
}
log.info("csv 解析完成: taskId={}, 共 {} 个轨迹点, 分 {} 批处理", taskId, allPath.size(), batchIndex);
GlobalMemory.taskPathMap.put(taskId, allPath);
sendMessage(taskId, allPath);
// TODO 推送数据到前端
return R.ok();
} catch (Exception e) {
log.error("csv.gz 解析失败: taskId={}, error={}", taskId, e.getMessage(), e);
return R.fail("文件解析失败: " + e.getMessage());
}
}
/**
* 定位信息 经纬度
*/
private void sendMessage(Long taskId, List<LatAndLngEntity> data) {
try {
WebRouteStatusMessageDTO dto = new WebRouteStatusMessageDTO();
String deviceId = deviceSessionManager.getDeviceByTaskId(taskId);
log.debug("sendMessage: taskId={}, deviceId={}", taskId, deviceId);
List<NettyDevice> controlMasters = deviceSessionManager
.getAllSlaveControl(deviceId);
log.debug("sendMessage: controlMasters={}", JsonUtils.toJsonString(controlMasters));
dto.setData(data);
dto.setDeviceId(deviceId);
dto.setTaskId(taskId);
dto.setType(MesType.route_post);
if (!CollectionUtils.isEmpty(controlMasters)) {
for (NettyDevice x : controlMasters) {
try {
websocketMesDispather.dispather(x.getConnectorId(), JsonUtils.toJsonString(dto));
log.info("sendMessage推送成功: connectorId={}, taskId={}", x.getConnectorId(), taskId);
} catch (Exception e) {
log.error("sendMessage推送失败: connectorId={}, error={}", x.getConnectorId(),
e.getMessage());
}
}
}
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId,
String.format(MqttTopic.DEVICE_ROUTE_PUSH_TOPIC, deviceId),
JsonUtils.toJsonString(dto));
} catch (Exception e) {
log.error("sendMessage失败: taskId={}", taskId, e);
}
}
/**
* 从 CSV 行解析 LatAndLngEntity
* 第 3 列(x) → lat,第 4 列(y) → lng
*/
private LatAndLngEntity parseLatLng(String[] values) {
try {
String xStr = values[2].trim();
String yStr = values[3].trim();
if (xStr.isEmpty() || yStr.isEmpty()) {
return null;
}
LatAndLngEntity entity = new LatAndLngEntity();
entity.setLat(Double.parseDouble(xStr));
entity.setLng(Double.parseDouble(yStr));
return entity;
} catch (NumberFormatException e) {
log.warn("解析轨迹点失败: values={}, error={}", Arrays.toString(values), e.getMessage());
return null;
}
}
/**
* 分批处理轨迹数据
*
* <p>
* 每批 BATCH_SIZE 条数据触发一次,可用于:
* </p>
* <ul>
* <li>批量入库(insertBatch)</li>
* <li>发布 MQTT 消息到上位机</li>
* <li>数据校验/统计</li>
* </ul>
*
* @param taskId 任务ID
* @param path 当前批次的轨迹点列表
* @param batchIndex 批次序号(从 1 开始)
*/
protected void processBatch(Long taskId, List<LatAndLngEntity> path, int batchIndex) {
if (path == null || path.isEmpty()) {
return;
}
int size = path.size();
double minLat = Double.MAX_VALUE, maxLat = -Double.MAX_VALUE;
double minLng = Double.MAX_VALUE, maxLng = -Double.MAX_VALUE;
for (LatAndLngEntity p : path) {
if (p.getLat() < minLat)
minLat = p.getLat();
if (p.getLat() > maxLat)
maxLat = p.getLat();
if (p.getLng() < minLng)
minLng = p.getLng();
if (p.getLng() > maxLng)
maxLng = p.getLng();
}
log.info("【第 {} 批处理】taskId={}, 本批 {} 条, lat范围=[{}, {}], lng范围=[{}, {}]",
batchIndex, taskId, size,
String.format("%.6f", minLat), String.format("%.6f", maxLat),
String.format("%.6f", minLng), String.format("%.6f", maxLng));
}
}

View File

@@ -3,6 +3,8 @@ package com.maibu.controller;
import java.io.InputStream;
import java.util.List;
import com.maibu.core.domain.AjaxResult;
import com.maibu.memory.GlobalMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
@@ -38,9 +40,11 @@ public class WorkRecordController extends BaseController {
) {
int i = -1;
// WorkRecord workRecordObj = JsonUtils.parseObject(workRecord, WorkRecord.class);
// WorkRecord workRecordObj = JsonUtils.parseObject(workRecord,
// WorkRecord.class);
WorkRecord workRecord1 = workRecordService.selectWorkRecordByWorkNameAndSiteId(workRecord.getWorkName(), workRecord.getSiteId());
WorkRecord workRecord1 = workRecordService.selectWorkRecordByWorkNameAndSiteId(workRecord.getWorkName(),
workRecord.getSiteId());
if (workRecord1 != null) {
return R.fail("该任务已存在");
}
@@ -106,8 +110,13 @@ public class WorkRecordController extends BaseController {
@GetMapping("/selectByTaskId")
public R selectByTaskId(@RequestParam Long taskId) {
List<LatAndLngEntity> path = workRecordService.selectByTaskId(taskId);
return R.ok(path);
WorkRecord workRecord = workRecordService.selectByTaskId(taskId);
return R.ok(workRecord);
}
@GetMapping("getTaskPath")
public AjaxResult getTaskPath(@RequestParam Long taskId) {
return AjaxResult.success(GlobalMemory.taskPathMap.get(taskId));
}
}

View File

@@ -16,7 +16,6 @@ import java.util.List;
@Repository
public interface DeviceMapper extends BaseMapperX<Device> {
/**
* 根据设备编号查询设备
*
@@ -25,7 +24,6 @@ public interface DeviceMapper extends BaseMapperX<Device> {
*/
public Device selectDeviceBySerialNumber(String serialNumber);
/**
* 查询设备列表
*
@@ -34,7 +32,6 @@ public interface DeviceMapper extends BaseMapperX<Device> {
*/
public List<Device> selectDeviceList(Device device);
/**
* 根据产品ID查询产品下所有设备的编号
*
@@ -66,7 +63,6 @@ public interface DeviceMapper extends BaseMapperX<Device> {
*/
public int updateDevice(Device device);
/**
* 重置设备状态
*
@@ -74,7 +70,6 @@ public interface DeviceMapper extends BaseMapperX<Device> {
*/
public int resetDeviceStatus(String deviceNum);
/**
* 根据产品ID获取产品下所有编号
*
@@ -91,4 +86,6 @@ public interface DeviceMapper extends BaseMapperX<Device> {
*/
public List<String> selectSerialNumbersByProductId(@Param("productId") Long productId);
public Device selectDeviceBySpiltCode(@Param("code") String code);
}

View File

@@ -16,10 +16,8 @@ import java.util.List;
*/
public interface IDeviceService {
public AjaxResult updateDeviceBySelf(Device device);
public Device insertDeviceBySelf(Device device);
/**
@@ -30,7 +28,6 @@ public interface IDeviceService {
*/
public Device selectDeviceBySerialNumber(String serialNumber);
/**
* 查询设备列表
*
@@ -39,7 +36,6 @@ public interface IDeviceService {
*/
public List<Device> selectDeviceList(Device device);
/**
* 根据产品ID查询产品下所有设备的编号
*
@@ -55,7 +51,6 @@ public interface IDeviceService {
*/
public int selectDeviceCountByProductId(Long productId);
/**
* 重置设备状态
*
@@ -63,7 +58,6 @@ public interface IDeviceService {
*/
public int resetDeviceStatus(String deviceNum);
/**
* 根据产品ID获取产品下所有编号
*
@@ -72,7 +66,6 @@ public interface IDeviceService {
*/
public String[] getDeviceNumsByProductId(Long productId);
/**
* 查询产品下所有设备,返回设备编号
*
@@ -81,17 +74,16 @@ public interface IDeviceService {
*/
public List<String> selectSerialNumbersByProductId(Long productId);
public boolean bind(DeviceBindDTO bindDTO, String username);
public boolean unbind(DeviceBindDTO bindDTO, String username);
public List<Device> getSiteList(LoginUser loginUser, Device device);
public List<Device> getDeviceList(LoginUser loginUser, Device device);
public Device getDeviceBySpiltCode(String code);
public boolean paramModifyAuth(DeviceParamAuthDTO dto);
public boolean hasPermission(String deviceId);

View File

@@ -13,6 +13,7 @@ import com.maibu.core.enums.AlarmType;
import com.maibu.core.redis.RedisCache;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -51,6 +52,10 @@ public class AlarmServiceImpl extends ServiceImpl<AlarmMapper, Alarm> implements
@Autowired
private RedisCache redisCache;
@Value("${mqtt.client-id}")
private String mqttClientId;
@Override
public List<Alarm> selectAlarmPage(AlarmQueryDTO queryDTO) {
// validateQueryParams(queryDTO);
@@ -407,7 +412,8 @@ public class AlarmServiceImpl extends ServiceImpl<AlarmMapper, Alarm> implements
public void pushErrorMessage(String deviceId, String dto) {
try {
GlobalMemory.mqttClientUtil.publish(String.format(MqttTopic.DEVICE_ERROR_PUSH_TOPIC, deviceId), dto);
GlobalMemory.customMqttDeviceMonitor.publish(mqttClientId, String.format(MqttTopic.DEVICE_ERROR_PUSH_TOPIC, deviceId), dto);
// GlobalMemory.mqttClientUtil.publish(String.format(MqttTopic.DEVICE_ERROR_PUSH_TOPIC, deviceId), dto);
// List<NettyDevice> controlMasters =
// sessionManager.getAllSlaveControl(deviceId);
// if (!CollectionUtils.isEmpty(controlMasters)) {

View File

@@ -1,5 +1,21 @@
package com.maibu.service.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.maibu.constant.Constant;
import com.maibu.core.business.DeviceRunParam;
@@ -10,17 +26,8 @@ import com.maibu.mapper.DeviceRunParamMapper;
import com.maibu.memory.DeviceSessionManager;
import com.maibu.memory.GlobalMemory;
import com.maibu.memory.SiteMemory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
@@ -32,7 +39,8 @@ public class DeviceRunParamService {
@Autowired
private DeviceSessionManager sessionManager;
public boolean save(DeviceRunParam deviceRunParam) throws ExecutionException, InterruptedException, TimeoutException {
public boolean save(DeviceRunParam deviceRunParam)
throws ExecutionException, InterruptedException, TimeoutException {
NettyDevice nettyDevice = sessionManager.getDevice(deviceRunParam.getDeviceId());
if (nettyDevice == null || !nettyDevice.getChannel().isActive()) {
@@ -87,30 +95,73 @@ public class DeviceRunParamService {
return deviceRunParamMapper.selectList(queryWrapper);
}
public DeviceRunParam selectByDeviceId(String deviceId) throws ExecutionException, InterruptedException, TimeoutException {
NettyDevice nettyDevice = sessionManager.getDevice(deviceId);
CompletableFuture<DeviceRunParam> future = new CompletableFuture<>();
if (nettyDevice != null && nettyDevice.getChannel() != null && nettyDevice.getChannel().isActive()) {
nettyDevice.getChannel().attr(Constant.READ_CONFIG_KEY).set(future);
try {
nettyDevice.readConfigCommand();
DeviceRunParam param = future.get(5, TimeUnit.SECONDS);
// public DeviceRunParam selectByDeviceId(String deviceId)
// throws ExecutionException, InterruptedException, TimeoutException {
LambdaQueryWrapper<DeviceRunParam> queryWrapper = new LambdaQueryWrapper<>();
if (!StringUtils.isEmpty(deviceId)) {
queryWrapper.eq(DeviceRunParam::getDeviceId, deviceId);
// NettyDevice nettyDevice = sessionManager.getDevice(deviceId);
// CompletableFuture<DeviceRunParam> future = new CompletableFuture<>();
// if (nettyDevice != null && nettyDevice.getChannel() != null &&
// nettyDevice.getChannel().isActive()) {
// nettyDevice.getChannel().attr(Constant.READ_CONFIG_KEY).set(future);
// try {
// nettyDevice.readConfigCommand();
// DeviceRunParam param = future.get(5, TimeUnit.SECONDS);
// LambdaQueryWrapper<DeviceRunParam> queryWrapper = new LambdaQueryWrapper<>();
// if (!StringUtils.isEmpty(deviceId)) {
// queryWrapper.eq(DeviceRunParam::getDeviceId, deviceId);
// }
// DeviceRunParam result = deviceRunParamMapper.selectOne(queryWrapper);
// if (result != null) {
// param.setId(result.getId());
// param.setDeviceId(result.getDeviceId());
// param.setRunSpeed(result.getRunSpeed());
// param.setOrgId(nettyDevice.getDevice().getOrgId());
// param.setSiteId(nettyDevice.getDevice().getSiteId());
// }
// return param;
// } finally {
// nettyDevice.getChannel().attr(Constant.READ_CONFIG_KEY).set(null);
// }
// }
// return null;
// }
public DeviceRunParam selectByDeviceId(String deviceId)
throws ExecutionException, InterruptedException, TimeoutException {
LambdaQueryWrapper<DeviceRunParam> queryWrapper = new LambdaQueryWrapper<>();
if (!StringUtils.isEmpty(deviceId)) {
queryWrapper.eq(DeviceRunParam::getDeviceId, deviceId);
}
DeviceRunParam result = deviceRunParamMapper.selectOne(queryWrapper);
if (result != null && result.getHasHost() != null && result.getHasHost() == 1) {
return result;
} else {
NettyDevice nettyDevice = sessionManager.getDevice(deviceId);
CompletableFuture<DeviceRunParam> future = new CompletableFuture<>();
if (nettyDevice != null && nettyDevice.getChannel() != null && nettyDevice.getChannel().isActive()) {
nettyDevice.getChannel().attr(Constant.READ_CONFIG_KEY).set(future);
try {
nettyDevice.readConfigCommand();
DeviceRunParam param = future.get(5, TimeUnit.SECONDS);
// LambdaQueryWrapper<DeviceRunParam> queryWrapper = new LambdaQueryWrapper<>();
// if (!StringUtils.isEmpty(deviceId)) {
// queryWrapper.eq(DeviceRunParam::getDeviceId, deviceId);
// }
// DeviceRunParam result = deviceRunParamMapper.selectOne(queryWrapper);
if (result != null) {
param.setId(result.getId());
param.setDeviceId(result.getDeviceId());
param.setRunSpeed(result.getRunSpeed());
param.setOrgId(nettyDevice.getDevice().getOrgId());
param.setSiteId(nettyDevice.getDevice().getSiteId());
}
return param;
} finally {
nettyDevice.getChannel().attr(Constant.READ_CONFIG_KEY).set(null);
}
DeviceRunParam result = deviceRunParamMapper.selectOne(queryWrapper);
if (result != null) {
param.setId(result.getId());
param.setDeviceId(result.getDeviceId());
param.setRunSpeed(result.getRunSpeed());
param.setOrgId(nettyDevice.getDevice().getOrgId());
param.setSiteId(nettyDevice.getDevice().getSiteId());
}
return param;
} finally {
nettyDevice.getChannel().attr(Constant.READ_CONFIG_KEY).set(null);
}
}
return null;
@@ -130,5 +181,56 @@ public class DeviceRunParamService {
return i;
}
public boolean importParam(List<DeviceRunParam> params) {
try {
if (!CollectionUtils.isEmpty(params)) {
List<String> snList = params.stream().map(DeviceRunParam::getDeviceId).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(snList)) {
LambdaQueryWrapper<DeviceRunParam> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(DeviceRunParam::getDeviceId, snList);
List<DeviceRunParam> oldList = deviceRunParamMapper.selectList(queryWrapper);
Map<String, DeviceRunParam> map = oldList.stream()
.collect(Collectors.toMap(
DeviceRunParam::getDeviceId,
Function.identity()));
List<DeviceRunParam> saveList = new ArrayList<>();
params.forEach(x -> {
if (StringUtils.isEmpty(x.getDeviceId()))
return;
DeviceRunParam deviceRunParam = null;
if (!CollectionUtils.isEmpty(map)) {
deviceRunParam = map.get(x.getDeviceId());
}
if (!StringUtils.isEmpty(x.getCapabilities())) {
List<String> list = Arrays.stream(x.getCapabilities().split(","))
.map(String::trim)
.collect(Collectors.toList());
x.setCapabilityList(list);
}
if (deviceRunParam != null) {
deviceRunParam.setCapabilityList(x.getCapabilityList());
deviceRunParam.setProductModel(x.getProductModel());
deviceRunParam.setRtkMode(x.getRtkMode());
deviceRunParam.setDeviceType(x.getDeviceType());
deviceRunParam.setPowerType(x.getPowerType());
deviceRunParam.setCameraNum(x.getCameraNum());
deviceRunParam.setWheelType(x.getWheelType());
deviceRunParam.setEngineType(x.getEngineType());
deviceRunParam.setHasHost(x.getHasHost());
saveList.add(deviceRunParam);
} else {
saveList.add(x);
}
});
return deviceRunParamMapper.saveOrUpdateBatch(saveList);
}
}
return false;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -8,12 +8,11 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import com.maibu.constant.CacheConstants;
import com.maibu.dto.DeviceParamAuthDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,8 +21,8 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import com.maibu.constant.CacheConstants;
import com.maibu.constant.NettyCacheKey;
import com.maibu.core.business.Device;
import com.maibu.core.business.DevicePlan;
import com.maibu.core.business.DeviceRunningStatusHistory;
@@ -34,13 +33,13 @@ import com.maibu.core.domain.entity.SysRole;
import com.maibu.core.domain.entity.SysSite;
import com.maibu.core.domain.model.LoginUser;
import com.maibu.core.enums.DeviceTaskStaus;
import com.maibu.core.host.LocationMessage;
import com.maibu.core.redis.RedisCache;
import com.maibu.influxdb.MowerRealTimeData;
import com.maibu.influxdb.util.InfluxSqlBuilder;
import com.maibu.influxdb.util.LambdaUtils;
import com.maibu.dto.DeviceParamAuthDTO;
import com.maibu.mapper.DeviceMapper;
import com.maibu.mapper.DevicePlanMapper;
import com.maibu.mapper.DeviceStatusHistoryMapper;
import com.maibu.mapper.HostLocationMapper;
import com.maibu.mapper.SysSiteMapper;
import com.maibu.memory.GlobalMemory;
import com.maibu.memory.SiteMemory;
@@ -75,6 +74,9 @@ public class DeviceServiceImpl implements IDeviceService {
@Autowired
private SysSiteMapper sysSiteMapper;
@Autowired
private HostLocationMapper hostLocationMapper;
/**
* 根据设备编号查询设备
*
@@ -147,6 +149,13 @@ public class DeviceServiceImpl implements IDeviceService {
Map<String, List<DeviceRunningStatusHistory>> statusMap = allStatusList.stream()
.collect(Collectors.groupingBy(DeviceRunningStatusHistory::getDeviceId));
LambdaQueryWrapper<LocationMessage> query = new LambdaQueryWrapper<>();
query.in(LocationMessage::getDeviceId, ids);
List<LocationMessage> hostLocations = hostLocationMapper.selectList(query);
Map<String, LocationMessage> locationMap = hostLocations.stream()
.collect(Collectors.toMap(LocationMessage::getDeviceId, Function.identity()));
deviceList.forEach(x -> {
Integer s = x.getOnlineStatus() == null ? 0 : x.getOnlineStatus();
if (0 == s) {
@@ -183,6 +192,13 @@ public class DeviceServiceImpl implements IDeviceService {
}
}
x.setLastRunningStatus(lastRunningStatus);
String locationKey = NettyCacheKey.latestPositonKey + x.getDeviceName();
LocationMessage locationMessage = redisCache.getCacheObject(locationKey);
if (locationMessage == null) {
locationMessage = locationMap.get(x.getDeviceName());
}
x.setLocationMessage(locationMessage);
});
deviceList.sort(Comparator.comparing(Device::getFeStatus));
}
@@ -312,13 +328,24 @@ public class DeviceServiceImpl implements IDeviceService {
if (!CollectionUtils.isEmpty(deviceList)) {
List<String> ids = deviceList.stream().map(Device::getDeviceName).collect(Collectors.toList());
List<DeviceRunningStatusHistory> allStatusList = deviceStatusHistoryMapper.selectLatestByDeviceIds(ids);
// 有上位机的
LambdaQueryWrapper<LocationMessage> query = new LambdaQueryWrapper<>();
query.in(LocationMessage::getDeviceId, ids);
List<LocationMessage> hostLocations = hostLocationMapper.selectList(query);
Map<String, LocationMessage> locationMap = hostLocations.stream()
.collect(Collectors.toMap(LocationMessage::getDeviceId, Function.identity()));
for (Device x : deviceList) {
if (siteMemory.hasTask(x.getSerialNumber())) {
work += 1;
x.setFeStatus(1);
} else {
free += 1;
x.setFeStatus(2);
if (x.getOnlineStatus() == null || x.getOnlineStatus() == 0) {
x.setFeStatus(3);
} else {
x.setFeStatus(2);
}
}
String key = "running_status:" + x.getDeviceName();
DeviceStatusRecordDTO recordDTO = redisCache.getCacheObject(key);
@@ -344,7 +371,16 @@ public class DeviceServiceImpl implements IDeviceService {
}
}
}
x.setLastRunningStatus(lastRunningStatus);
String locationKey = NettyCacheKey.latestPositonKey + x.getDeviceName();
LocationMessage locationMessage = redisCache.getCacheObject(locationKey);
if (locationMessage == null) {
locationMessage = locationMap.get(x.getDeviceName());
}
x.setLocationMessage(locationMessage);
}
statusStatistics.put(0, work);
statusStatistics.put(1, free);
@@ -353,7 +389,9 @@ public class DeviceServiceImpl implements IDeviceService {
});
}
}
deviceList.sort(Comparator.comparing(Device::getFeStatus));
if (!CollectionUtils.isEmpty(deviceList)) {
deviceList.sort(Comparator.comparing(Device::getFeStatus));
}
return deviceList;
}
@@ -402,4 +440,9 @@ public class DeviceServiceImpl implements IDeviceService {
return false;
}
@Override
public Device getDeviceBySpiltCode(String code) {
return deviceMapper.selectDeviceBySpiltCode(code);
}
}

View File

@@ -0,0 +1,18 @@
package com.maibu.service.impl;
import com.maibu.core.business.WorkRecord;
import com.maibu.memory.GlobalMemory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class HostComputerService {
@Autowired
private WorkRecordService workRecordService;
}

View File

@@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Comparator;
import java.util.List;
@Service
@@ -64,6 +65,7 @@ public class WorkRecordService {
list.forEach(workRecord -> {
workRecord.setJsonData(null);
});
list.sort(Comparator.comparing(WorkRecord::getId).reversed());
}
return list;
}
@@ -72,16 +74,13 @@ public class WorkRecordService {
return workRecordMapper.selectById(id);
}
public List<LatAndLngEntity> selectByTaskId(Long taskId) {
public WorkRecord selectByTaskId(Long taskId) {
DevicePlanTask devicePlanTask = devicePlanTaskMapper.selectById(taskId); // 先查询设备任务,确认任务存在
if (devicePlanTask == null) {
return null;
}
Long routeId = devicePlanTask.getRouteId(); // 获取任务关联的路线ID
WorkRecord workRecord = workRecordMapper.selectById(routeId); // 查询路线ID对应的工作记录
if (workRecord != null && workRecord.getJsonData() != null) {
return workRecord.getJsonData().getPath(); // 返回路线数据
}
return null; // 如果没有找到对应的工作记录或路线数据,返回 null
return workRecord; // 如果没有找到对应的工作记录或路线数据,返回 null
}
}

View File

@@ -39,10 +39,14 @@
<result property="guid" column="guid"/>
<result property="protocolCode" column="protocol_code"/>
<result property="onlineStatus" column="online_status"/>
<result property="hasHost" column="has_host"/>
<result property="hasRegistered" column="has_registered"/>
</resultMap>
<sql id="selectDeviceVo">
select d.online_status,
d.has_host,
d.has_registered,
d.device_alias,
d.device_id,
d.org_id,
@@ -145,6 +149,8 @@
and d.active_time between #{params.beginActiveTime} and #{params.endActiveTime}
</if>
<if test="onlineStatus != null ">and d.online_status = #{onlineStatus}</if>
<if test="hasHost != null ">and d.has_host = #{hasHost}</if>
<if test="hasRegistered != null ">and d.has_registered = #{hasRegistered}</if>
</where>
order by d.create_time desc
</select>
@@ -302,6 +308,8 @@
<if test="onlineStatus != null">online_status,</if>
<if test="orgId != null">org_id,</if>
<if test="siteId != null">site_id,</if>
<if test="hasHost != null">has_host,</if>
<if test="hasRegistered != null">has_registered,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceName != null and deviceName != ''">#{deviceName},</if>
@@ -335,6 +343,8 @@
<if test="onlineStatus != null">#{onlineStatus},</if>
<if test="orgId != null">#{orgId},</if>
<if test="siteId != null">#{siteId},</if>
<if test="hasHost != null">#{hasHost},</if>
<if test="hasRegistered != null">#{hasRegistered},</if>
</trim>
</insert>
@@ -395,6 +405,8 @@
<if test="onlineStatus != null">online_status = #{onlineStatus},</if>
<if test="orgId != null">org_id = #{orgId},</if>
<if test="siteId != null">site_id = #{siteId},</if>
<if test="hasHost != null">has_host = #{hasHost},</if>
<if test="hasRegistered != null">has_registered = #{hasRegistered},</if>
</trim>
where device_id = #{deviceId}
</update>
@@ -492,4 +504,9 @@
#{deviceId}
</foreach>
</update>
<select id="selectDeviceBySpiltCode" parameterType="String" resultMap="DeviceResult">
<include refid="selectDeviceVo"/>
where device_name like concat('%', #{code}, '%')
</select>
</mapper>

View File

@@ -4,21 +4,20 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.maibu.constant.Constant;
import com.maibu.core.business.DeviceModelPropertyMapping;
import com.maibu.core.business.DevicePlanTask;
import com.maibu.core.business.IoTCommonDevice;
import com.maibu.core.business.Product;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.dto.DeviceConfigDTO;
import com.maibu.core.enums.ConnectType;
import com.maibu.mapper.IotDeviceCommonMapper;
import com.maibu.mapper.SysDeviceModelPropertyMappingMapper;
import com.maibu.memory.GlobalMemory;
import com.maibu.memory.SiteMemory;
import com.maibu.mqtt.*;
import com.maibu.mqtt.CustomMqttDeviceConfig;
import com.maibu.mqtt.CustomMqttDeviceMonitor;
import com.maibu.mqtt.GenericPropertyMessageHandler;
import com.maibu.mqtt.PropertyMappingConfig;
import com.maibu.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -26,14 +25,13 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.PreDestroy;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -46,13 +44,15 @@ import java.util.concurrent.TimeUnit;
@Service
public class IotDeviceCommonService {
@Autowired
private IotDeviceCommonMapper iotDeviceCommonMapper;
@Autowired
private SysDeviceModelPropertyMappingMapper sysDeviceModelPropertyMappingMapper;
@Autowired
private ScheduledExecutorService executor;
public void save(IoTCommonDevice ioTCommonDevice) throws Exception {
iotDeviceCommonMapper.saveOrUpdate(ioTCommonDevice);
SiteMemory siteMemory = GlobalMemory.getSiteMemory(ioTCommonDevice.getOrgId(), ioTCommonDevice.getSiteId());
@@ -91,16 +91,13 @@ public class IotDeviceCommonService {
device.setDelFlag(false);
device.setCreateTime(LocalDateTime.now());
log.info("设备 {} 已加入未注册缓存", deviceDTO.getSn());
}
public List<IoTCommonDevice> getUnRegisterDevices() {
return new ArrayList<>(GlobalMemory.unRegisterCommonDeviceMap.values());
}
public Page<IoTCommonDevice> selectDevicePage(Page<IoTCommonDevice> page, IoTCommonDevice device) {
LambdaQueryWrapper<IoTCommonDevice> wrapper = new LambdaQueryWrapper<>();
@@ -122,7 +119,6 @@ public class IotDeviceCommonService {
return iotDeviceCommonMapper.selectPage(page, wrapper);
}
public IoTCommonDevice getBySn(String sn) {
LambdaQueryWrapper<IoTCommonDevice> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IoTCommonDevice::getDeviceId, sn)
@@ -130,7 +126,6 @@ public class IotDeviceCommonService {
return iotDeviceCommonMapper.selectOne(wrapper);
}
public void registerDevice(IoTCommonDevice device) {
device.setHasRegister(true);
device.setUpdateTime(LocalDateTime.now());
@@ -143,7 +138,6 @@ public class IotDeviceCommonService {
GlobalMemory.unRegisterCommonDeviceMap.remove(device.getSn());
}
@Transactional(rollbackFor = Exception.class)
public boolean updateDevice(DeviceConfigDTO deviceDTO) {
// 1. 查询设备是否存在
@@ -180,7 +174,6 @@ public class IotDeviceCommonService {
return iotDeviceCommonMapper.saveOrUpdate(device);
}
/**
* 设备连接测试(根据协议类型)
*/
@@ -200,12 +193,10 @@ public class IotDeviceCommonService {
}
}
public IoTCommonDevice getFromUnRegisterCache(String sn) {
return GlobalMemory.unRegisterCommonDeviceMap.get(sn);
}
public void updateUnRegisterCache(IoTCommonDevice device) {
if (device.getDeviceId() == null) {
throw new RuntimeException("设备 ID (SN) 不能为空");
@@ -215,14 +206,14 @@ public class IotDeviceCommonService {
log.info("未注册设备 {} 缓存配置已更新", device.getDeviceId());
}
/**
* MQTT 连接测试
*/
private boolean testMqttConnection(String ip, Integer port, String username, String password) {
try {
// TODO: 使用 MQTT 客户端测试连接
// MqttClient client = new MqttClient("tcp://" + ip + ":" + port, "test-" + UUID.randomUUID());
// MqttClient client = new MqttClient("tcp://" + ip + ":" + port, "test-" +
// UUID.randomUUID());
// ...
return true; // 暂时返回 true,后续接入真实 MQTT 库
} catch (Exception e) {
@@ -257,41 +248,28 @@ public class IotDeviceCommonService {
}
}
public void removeByIds(List<Long> ids) {
if (CollectionUtils.isEmpty(ids)) return;
if (CollectionUtils.isEmpty(ids))
return;
iotDeviceCommonMapper.deleteBatchIds(ids);
}
// 3. 按 productId 分组,缓存映射配置避免重复查询
public static Map<Long, List<DeviceModelPropertyMapping>> mappingCache = new ConcurrentHashMap<>();
public void commonDeviceMonitor() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(() -> {
try {
CustomMqttDeviceMonitor monitor = GlobalMemory.customMqttDeviceMonitor;
GlobalMemory.getAllSiteMemory().forEach(siteMemory -> {
CustomMqttDeviceMonitor monitor = siteMemory.getCustomMqttDeviceMonitor();
if (monitor == null) {
// 1. 创建 MQTT 监控器和消息处理器
monitor = new CustomMqttDeviceMonitor();
monitor.registerHandler(new GenericPropertyMessageHandler());
monitor.registerHandler(new ActionMessageHandler());
siteMemory.setCustomMqttDeviceMonitor(monitor);
}
// 2. 查询所有外部设备
List<IoTCommonDevice> deviceList = siteMemory.getAllCommonDevice();
if (CollectionUtils.isEmpty(deviceList)) {
return;
}
for (IoTCommonDevice device : deviceList) {
if (!ConnectType.MQTT.equals(device.getConnectType())) {
continue;
@@ -311,7 +289,8 @@ public class IotDeviceCommonService {
// 注册到处理器,用 productId 区分不同产品的映射
String handlerType = "property-" + productId;
GenericPropertyMessageHandler handler = (GenericPropertyMessageHandler) monitor.getMessageHandler(handlerType);
GenericPropertyMessageHandler handler = (GenericPropertyMessageHandler) monitor
.getMessageHandler(handlerType);
handler.registerMappingConfig(handlerType, mappingConfig);
// 获取产品信息
@@ -333,8 +312,6 @@ public class IotDeviceCommonService {
monitor.registerDevice(deviceConfig);
}
siteMemory.setCustomMqttDeviceMonitor(monitor);
monitor.start();
});
} catch (Exception e) {
@@ -347,11 +324,14 @@ public class IotDeviceCommonService {
/**
* 根据数据库映射列表构建 PropertyMappingConfig
*
* <p>将 {@link DeviceModelPropertyMapping} 转换为 {@link PropertyMappingConfig.FieldMapping}:</p>
* <p>
* 将 {@link DeviceModelPropertyMapping} 转换为
* {@link PropertyMappingConfig.FieldMapping}:
* </p>
* <ul>
* <li>sourceKey → FieldMapping.source(外部 JSON 中的字段名)</li>
* <li>standardKey → FieldMapping.target(统一后的内部属性名)</li>
* <li>dataType → FieldMapping.valueType(自动类型转换)</li>
* <li>sourceKey → FieldMapping.source(外部 JSON 中的字段名)</li>
* <li>standardKey → FieldMapping.target(统一后的内部属性名)</li>
* <li>dataType → FieldMapping.valueType(自动类型转换)</li>
* </ul>
*/
private PropertyMappingConfig buildMappingConfig(List<DeviceModelPropertyMapping> mappings) {

View File

@@ -62,10 +62,12 @@ public class WebMiddlewareController extends BaseController {
List<String> nettyClients = new ArrayList<>();
MiddleGlobalMemory.nettyClientMap.values().forEach(x -> {
if (x != null) {
nettyClients.add(x.userName +":"+ x.token);
nettyClients.add(x.userName + ":" + x.token);
}
});
res.put("nettyClientMap", nettyClients);
res.put("controllerMap", deviceSessionManager.getControllerMap());
res.put("taskPathMap", GlobalMemory.taskPathMap);
res.put("siteMemory", GlobalMemory.getAllSiteMemory());
return AjaxResult.success(res);
}

View File

@@ -23,4 +23,9 @@ public class NettySwitchDeviceDTO {
*/
private String userId;
/**
* 用户名
*/
private String token;
}

View File

@@ -8,6 +8,8 @@ import lombok.Data;
public class WebMessageBaseDTO {
private MesType type;
private String userName;
private Long siteId;
private Long orgId;
private String deviceId;
private String token;
private String message;

View File

@@ -9,8 +9,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
@@ -25,6 +24,8 @@ public class MiddleGlobalMemory {
*/
public static Vertx vertx;
//TODO 推送全局的时候需要 判断是不是单个的控制
public static ConcurrentHashMap<Long, Map<String, WebSocket>> mapPushMap = new ConcurrentHashMap<>();
// 存储所有在线WebSocket连接
// key: test:web:token
public static final ConcurrentHashMap<String, WebSocket> onlineSockets = new ConcurrentHashMap<>();
@@ -84,6 +85,7 @@ public class MiddleGlobalMemory {
.findFirst().orElse(null);
}
public static String findNettyClient(NettyClient nettyClient) {
return nettyClientMap.entrySet().stream()
.filter(entry -> Objects.equals(entry.getValue(), nettyClient))
@@ -91,5 +93,38 @@ public class MiddleGlobalMemory {
.findFirst().orElse(null);
}
public static void addMapSocket(Long siteId, String token, WebSocket webSocket) {
mapPushMap
.computeIfAbsent(siteId, k -> new ConcurrentHashMap<>())
.put(token, webSocket);
}
public static void removeByWebSocket(WebSocket webSocket) {
if (webSocket == null) {
return;
}
mapPushMap.forEach((siteId, socketMap) -> {
socketMap.entrySet().removeIf(entry ->
entry.getValue() == webSocket
);
// siteId 下没有连接了,删除整个 siteId
if (socketMap.isEmpty()) {
mapPushMap.remove(siteId, socketMap);
}
});
}
public static List<WebSocket> getMapSockets(Long siteId) {
Map<String, WebSocket> map = mapPushMap.get(siteId);
if (map == null || map.isEmpty()) {
return Collections.emptyList();
}
return new ArrayList<>(map.values());
}
}

View File

@@ -1,11 +1,13 @@
package com.maibu.memory;
import com.maibu.utils.spring.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.ThreadMXBean;
@@ -19,6 +21,8 @@ import java.util.concurrent.TimeUnit;
public class SystemMonitor {
private final Logger logger = LoggerFactory.getLogger(SystemMonitor.class);
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
// 启动监控任务
@PostConstruct
public void startMonitoring() {
@@ -37,7 +41,6 @@ public class SystemMonitor {
// 内存监控
private void startMemoryMonitor() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory() / (1024 * 1024);
@@ -59,7 +62,6 @@ public class SystemMonitor {
// CPU 监控
private void startCpuMonitor() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
try {
// 使用 OperatingSystemMXBean 获取 CPU 使用率
@@ -84,7 +86,6 @@ public class SystemMonitor {
// 线程监控
private void startThreadMonitor() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
int threadCount = threadBean.getThreadCount();
@@ -105,7 +106,6 @@ public class SystemMonitor {
// 连接数监控
private void startConnectionMonitor() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
int webSocketCount = MiddleGlobalMemory.onlineSockets.size();
int nettyClientCount = MiddleGlobalMemory.nettyClientMap.size();
@@ -114,4 +114,5 @@ public class SystemMonitor {
webSocketCount, nettyClientCount);
}, 1, 5, TimeUnit.MINUTES);
}
}

View File

@@ -4,8 +4,11 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import com.maibu.core.business.dto.WebDeviceTaskStatusMessageDTO;
import com.maibu.core.business.dto.WebStatusMessageDTO;
import org.apache.commons.lang3.StringUtils;
import org.java_websocket.WebSocket;
import org.slf4j.Logger;
@@ -21,9 +24,7 @@ import com.maibu.dto.DeviceRespondDTO;
import com.maibu.dto.NettySwitchDeviceDTO;
import com.maibu.dto.ResultDTO;
import com.maibu.dto.WebAuthResponseDTO;
import com.maibu.dto.WebDeviceTaskStatusMessageDTO;
import com.maibu.dto.WebOlineStatusMessageDTO;
import com.maibu.dto.WebStatusMessageDTO;
import com.maibu.dto.WebSwitchControlResponseDTO;
import com.maibu.memory.MiddleGlobalMemory;
import com.maibu.netty.NettyClient;
@@ -63,10 +64,10 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
ctx.writeAndFlush(data);
}
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
if (nettyClient != null && StringUtils.isEmpty(nettyClient.currentDeviceId)) {
if (nettyClient != null) {
if (nettyClient.lastSwitchTime == -1
|| System.currentTimeMillis() - nettyClient.lastSwitchTime > nettyClient.Interval) {
if (!StringUtils.isEmpty(nettyClient.requestDeviceId) && !StringUtils.isEmpty(key)) {
if (!StringUtils.isEmpty(nettyClient.requestDeviceId) && !StringUtils.isEmpty(key) && !Objects.equals(nettyClient.requestDeviceId, nettyClient.currentDeviceId)) {
switchDevice(nettyClient, key);
nettyClient.lastSwitchTime = System.currentTimeMillis();
}
@@ -138,7 +139,7 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
}
} else {
/* 判断 respond 字段 */
if (obj.get("request") != null) {
if (obj.get("request") != null && !"null".equals(obj.get("request").toString())) {
String deviceId = obj.get("deviceId").toString();
String userName = obj.get("userId").toString();
String platform = obj.get("platform").toString();
@@ -151,7 +152,7 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
logger.debug("json: {}", JsonUtils.toJsonString(request));
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(request));
} else {
if (obj.get("respond") != null) {
if (obj.get("respond") != null && !"null".equals(obj.get("respond").toString())) {
if ("have_logged_in".equals(obj.get("respond").toString())) {
/* 如果回复 have_logged_in 则调用logout */
// System.out.println("检测到重复登录,执行 logout");
@@ -400,6 +401,7 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
NettySwitchDeviceDTO dto = new NettySwitchDeviceDTO();
dto.setDeviceId(nettyClient.requestDeviceId);
dto.setPlatform("web");
dto.setToken(token);
String result = httpService.doPost(postUrl, token, dto);
ResultDTO resultDTO = JsonUtils.parseObject(result, ResultDTO.class);
if (resultDTO != null && resultDTO.getData() != null) {
@@ -459,7 +461,9 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
// stopHeartbeat(nettyClient);
nettyClient.currentDeviceId = null;
if (nettyClient != null) {
nettyClient.currentDeviceId = null;
}
logger.debug("连接断开channelInactive,ChannelId:{} ", ctx.channel().id().asLongText());
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(key);
if (webSocket != null && webSocket.isOpen()) {

View File

@@ -10,6 +10,7 @@ import com.maibu.netty.NettyClient;
import com.maibu.utils.CommandUtils;
import com.maibu.utils.ControlUtils;
import com.maibu.utils.json.JsonUtils;
import com.maibu.utils.spring.SpringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.commons.lang3.StringUtils;
@@ -41,7 +42,7 @@ public class WebsocketHandler extends WebSocketServer {
// 空闲超时时间(10秒):超过10秒无消息则判定为断开
private static final long IDLE_TIMEOUT = 10 * 1000;
// 定时检测线程池(每隔10秒检测一次空闲连接)
private final ScheduledExecutorService idleCheckExecutor = Executors.newSingleThreadScheduledExecutor();
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
public WebsocketHandler(int port) {
@@ -63,6 +64,7 @@ public class WebsocketHandler extends WebSocketServer {
releaseResource(key);
logger.info("客户端断开连接: remoteAddress={}, code={}, reason={}, 当前在线数: {}", conn.getRemoteSocketAddress(), code, reason, MiddleGlobalMemory.onlineSockets.size());
}
MiddleGlobalMemory.removeByWebSocket(conn);
}
@Override
@@ -76,29 +78,27 @@ public class WebsocketHandler extends WebSocketServer {
String deviceId = webMessageBaseDTO.getDeviceId();
MesType type = webMessageBaseDTO.getType();
String key = userName + ":web" + ":" + token;
if (MesType.AUTH.equals(type)) {
if (MesType.mapPush.equals(type)) {
Long siteId = webMessageBaseDTO.getSiteId();
MiddleGlobalMemory.addMapSocket(siteId, token, conn);
} else if (MesType.AUTH.equals(type)) {
if (StringUtils.isEmpty(deviceId)) {
return;
}
//todo 先关闭原先的
WebSocket oldWebsocket = MiddleGlobalMemory.onlineSockets.get(key);
NettyClient oldNettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
if (oldWebsocket != null && oldWebsocket.isOpen()) {
oldWebsocket.close();
}
if (oldNettyClient != null && oldNettyClient.isConnected()) {
oldNettyClient.disconnect();
}
NettyClient client = MiddleGlobalMemory.nettyClientMap.get(key);
MiddleGlobalMemory.onlineSockets.put(key, conn);
//这里可以根据需要初始化Netty客户端
NettyClient client = new NettyClient();
if (client == null) {
client = new NettyClient();
}
client.key = key;
client.requestDeviceId = deviceId;
client.token = token;
client.userName = userName;
client.initClient();
client.initConnect(MiddleConstant.vertxIp, 9001);
MiddleGlobalMemory.nettyClientMap.put(key, client);
if (!client.isConnected()) {
client.initClient();
client.initConnect(MiddleConstant.vertxIp, 9001);
}
} else if (MesType.path.equals(type)) {
//todo下发路径 预留
pathDistribute();
@@ -126,7 +126,7 @@ public class WebsocketHandler extends WebSocketServer {
// else if(MesType.heartbeat.equals(type)){
// clientActiveTimeMap.put(conn,System.currentTimeMillis());
// }
else if(MesType.switchPermission.equals(type)){
else if (MesType.switchPermission.equals(type)) {
DeviceRequestDTO requestDTO = new DeviceRequestDTO();
requestDTO.setRequest(CommandRequestType.switch_control);
requestDTO.setToken(token);
@@ -136,12 +136,12 @@ public class WebsocketHandler extends WebSocketServer {
logger.debug("Netty客户端:{}发送操控请求数据: {}", userName, JsonUtils.toJsonString(requestDTO));
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
ByteBuf buf = Unpooled.buffer();
CommandUtils.buildCommand(buf,requestDTO, CommandConstant.interaction);
CommandUtils.buildCommand(buf, requestDTO, CommandConstant.interaction);
if (nettyClient != null && nettyClient.isConnected()) {
// if (!nettyClient.isConnected()) nettyClient.connect();
nettyClient.sendData(buf.array());
}
} else if(MesType.switchResult.equals(type)){
} else if (MesType.switchResult.equals(type)) {
DeviceRequestDTO requestDTO = new DeviceRequestDTO();
requestDTO.setToken(token);
requestDTO.setPlatform("web");
@@ -154,7 +154,7 @@ public class WebsocketHandler extends WebSocketServer {
logger.debug("Netty客户端:{}发送切换操控权请求反馈: {}", userName, JsonUtils.toJsonString(requestDTO));
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
ByteBuf buf = Unpooled.buffer();
CommandUtils.buildCommand(buf,requestDTO, CommandConstant.interaction);
CommandUtils.buildCommand(buf, requestDTO, CommandConstant.interaction);
if (nettyClient != null && nettyClient.isConnected()) {
// if (!nettyClient.isConnected()) nettyClient.connect();
nettyClient.sendData(buf.array());
@@ -225,7 +225,7 @@ public class WebsocketHandler extends WebSocketServer {
// 核心:定时检测空闲连接(每隔10秒执行一次)
private void startIdleCheckTask() {
idleCheckExecutor.scheduleAtFixedRate(() -> {
executor.scheduleAtFixedRate(() -> {
long now = System.currentTimeMillis();
// 遍历所有连接,检测是否超时
for (Map.Entry<WebSocket, Long> entry : clientActiveTimeMap.entrySet()) {

View File

@@ -1,19 +1,22 @@
package com.maibu.websocket;
import com.maibu.core.business.inter.DeviceWebSocketInterace;
import com.maibu.core.business.inter.DeviceWebSocketInterface;
import com.maibu.memory.MiddleGlobalMemory;
import org.java_websocket.WebSocket;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
@Component
public class WebsocketMesHandler implements DeviceWebSocketInterace {
public class WebsocketMesHandler implements DeviceWebSocketInterface {
@Override
public void sendWebsoketMes(String key, String data) {
public void sendWebsocketMes(String key, String data) {
MiddleGlobalMemory.onlineSockets.keySet().forEach(x -> {
// key: test:web:token
if (x.startsWith(key)) {
if (x.contains(key)) {
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(x);
WebsocketHandler.sendMessageToClient(webSocket, data);
}
@@ -21,4 +24,14 @@ public class WebsocketMesHandler implements DeviceWebSocketInterace {
}
@Override
public void sendMapWebsocketMes(Long siteId, String data) {
List<WebSocket> webSocketList = MiddleGlobalMemory.getMapSockets(siteId);
if (!CollectionUtils.isEmpty(webSocketList)) {
webSocketList.forEach(webSocket -> {
WebsocketHandler.sendMessageToClient(webSocket, data);
});
}
}
}