feat(sis): 新增轿厢电梯控制功能

This commit is contained in:
zcxlsm 2025-08-08 23:33:30 +08:00
parent 15531ae029
commit ec301ac993
6 changed files with 402 additions and 25 deletions

View File

@ -32,7 +32,7 @@ public class VideoAlarmController {
}
@PostMapping("/huawei/callback")
public void huaweiAlarm(@RequestBody String data) {
public void huaweiAlarm(@RequestBody Object data) {
log.info("华为上报消息,msg={}", data);
}

View File

@ -9,13 +9,13 @@ public enum ControlTypeEnum {
*/
ACCESS_CONTROL(1),
/**
* 远程呼梯
* 电梯外面面板权限
*/
REMOTE_CALL_ELEVATOR(2),
ELEVATOR_OUT_CONTROL(2),
/**
* 梯控
* 电梯里面的面板
*/
ELEVATOR_CONTROL(3);
ELEVATOR_IN_CONTROL(3);
private final Integer code;

View File

@ -0,0 +1,40 @@
package org.dromara.sis.runner;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sis.sdk.smartDevices.utils.ElevatorControlUtil;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author lsm
* @apiNote ElevatorTcpRunner
* @since 2025/8/8
*/
@Slf4j
@Component
public class ElevatorTcpRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
// 获取单例实例并启动服务
ElevatorControlUtil instance = ElevatorControlUtil.getInstance();
try{
// 启动服务
instance.start(23);
log.info("启动电梯控制服务成功");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// 停止服务
log.info("应用关闭中,停止电梯服务...");
instance.stop();
}));
} catch (IOException e) {
log.info("启动电梯控制服务失败");
}
}
}

View File

@ -0,0 +1,309 @@
package org.dromara.sis.sdk.smartDevices.utils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author lsm
* @apiNote ElevatorControlUtil
* @since 2025/8/8
*/
@Service
public class ElevatorControlUtil {
private static final int FRAME_LENGTH = 10;
private static final byte FIXED_ADDRESS = 0x00;
private static final byte MANUAL_COMMAND = (byte) 0xD1;
private static final byte AUTO_COMMAND = (byte) 0xD2;
private static final byte MANUAL_RESPONSE = (byte) 0xC1;
private static final byte AUTO_RESPONSE = (byte) 0xC2;
private static final byte HEARTBEAT_COMMAND = (byte) 0xD0;
private static ElevatorControlUtil instance;
private ServerSocket serverSocket;
private Socket clientSocket;
private InputStream inputStream;
private OutputStream outputStream;
private final Object lock = new Object();
private ScheduledExecutorService heartbeatScheduler;
private volatile boolean isRunning = false;
// 私有构造函数
private ElevatorControlUtil() {}
/**
* 获取单例实例
*/
public static synchronized ElevatorControlUtil getInstance() {
if (instance == null) {
instance = new ElevatorControlUtil();
}
return instance;
}
/**
* 启动TCP服务
* @param port 监听端口
*/
public void start(Integer port) throws IOException {
if (isRunning) {
throw new IllegalStateException("服务已在运行中");
}
serverSocket = new ServerSocket(port);
isRunning = true;
// 启动连接监听线程
new Thread(this::acceptConnections, "TCP-Acceptor").start();
System.out.println("电梯控制服务已启动,监听端口: " + port);
}
/**
* 停止服务
*/
public void stop() {
isRunning = false;
// 停止心跳
if (heartbeatScheduler != null) {
heartbeatScheduler.shutdown();
}
// 关闭连接
closeClientResources();
// 关闭服务器
if (serverSocket != null && !serverSocket.isClosed()) {
try {
serverSocket.close();
System.out.println("服务已停止");
} catch (IOException e) {
System.err.println("停止服务时出错: " + e.getMessage());
}
}
}
/**
* 接受客户端连接
*/
private void acceptConnections() {
while (isRunning) {
try {
clientSocket = serverSocket.accept();
clientSocket.setKeepAlive(true);
inputStream = clientSocket.getInputStream();
outputStream = clientSocket.getOutputStream();
System.out.println("梯控设备已连接: " + clientSocket.getRemoteSocketAddress());
// 启动心跳机制
startHeartbeat();
} catch (IOException e) {
if (isRunning) {
System.err.println("接受连接时出错: " + e.getMessage());
try {
Thread.sleep(5000); // 5秒后重试
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
}
}
/**
* 启动心跳机制
*/
private void startHeartbeat() {
if (heartbeatScheduler != null && !heartbeatScheduler.isShutdown()) {
heartbeatScheduler.shutdown();
}
heartbeatScheduler = Executors.newSingleThreadScheduledExecutor();
heartbeatScheduler.scheduleAtFixedRate(() -> {
if (isClientConnected()) {
try {
sendHeartbeat();
} catch (IOException e) {
System.err.println("心跳包发送失败: " + e.getMessage());
closeClientResources();
}
}
}, 30, 30, TimeUnit.SECONDS);
}
/**
* 发送心跳包
*/
private void sendHeartbeat() throws IOException {
synchronized (lock) {
if (!isClientConnected()) return;
byte[] heartbeatData = new byte[7];
byte[] frame = buildFrame(HEARTBEAT_COMMAND, heartbeatData);
outputStream.write(frame);
outputStream.flush();
}
}
/**
* 检查客户端是否连接
*/
public boolean isClientConnected() {
return clientSocket != null && clientSocket.isConnected() && !clientSocket.isClosed();
}
/**
* 发送手动选层命令
* @param floors 要开放的楼层列表
* @return 是否成功
*/
public boolean sendManualCommand(List<Integer> floors) {
return sendCommand(MANUAL_COMMAND, MANUAL_RESPONSE, generateFloorData(floors));
}
/**
* 发送自动选层命令
* @param floors 要开放的楼层列表
* @return 是否成功
*/
public boolean sendAutoCommand(List<Integer> floors) {
return sendCommand(AUTO_COMMAND, AUTO_RESPONSE, generateFloorData(floors));
}
/**
* 发送管理员权限命令开放所有楼层
*/
public boolean sendAdminCommand() {
byte[] allFloors = new byte[]{
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
};
return sendCommand(MANUAL_COMMAND, MANUAL_RESPONSE, allFloors);
}
private boolean sendCommand(byte command, byte expectedResponse, byte[] floorData) {
if (!isClientConnected()) {
System.err.println("发送命令失败:未连接梯控设备");
return false;
}
if (floorData == null || floorData.length != 7) {
System.err.println("发送命令失败:无效的楼层数据");
return false;
}
synchronized (lock) {
for (int attempt = 0; attempt < 2; attempt++) {
try {
byte[] frame = buildFrame(command, floorData);
outputStream.write(frame);
outputStream.flush();
// 设置100ms超时
clientSocket.setSoTimeout(100);
byte[] response = new byte[FRAME_LENGTH];
int bytesRead = 0;
while (bytesRead < FRAME_LENGTH) {
int read = inputStream.read(response, bytesRead, FRAME_LENGTH - bytesRead);
if (read == -1) {
throw new IOException("连接已关闭");
}
bytesRead += read;
}
if (validateResponse(response, expectedResponse)) {
return true;
}
} catch (java.net.SocketTimeoutException e) {
System.err.println("命令响应超时,尝试重试");
} catch (IOException e) {
System.err.println("发送命令时出错: " + e.getMessage());
closeClientResources();
return false;
}
}
return false;
}
}
/**
* 生成楼层数据
*/
public byte[] generateFloorData(List<Integer> floors) {
if (floors == null || floors.isEmpty()) {
return new byte[]{0, 0, 0, 0, 0, 0, 0};
}
byte[] data = new byte[7];
for (int floor : floors) {
if (floor < 1 || floor > 56) continue;
int index = 56 - floor;
int bytePos = index / 8;
int bitPos = 7 - (index % 8);
data[bytePos] |= (byte) (1 << bitPos);
}
return data;
}
private byte[] buildFrame(byte command, byte[] floorData) {
byte[] frame = new byte[FRAME_LENGTH];
frame[0] = command;
frame[1] = FIXED_ADDRESS;
System.arraycopy(floorData, 0, frame, 2, 7);
byte checksum = 0;
for (int i = 0; i < 9; i++) {
checksum = (byte) (checksum + frame[i]);
}
frame[9] = checksum;
return frame;
}
private boolean validateResponse(byte[] response, byte expectedHeader) {
if (response[0] != expectedHeader) return false;
if (response[1] != FIXED_ADDRESS) return false;
byte checksum = 0;
for (int i = 0; i < 9; i++) {
checksum = (byte) (checksum + response[i]);
}
return response[9] == checksum;
}
/**
* 关闭客户端资源
*/
private void closeClientResources() {
try {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
if (clientSocket != null && !clientSocket.isClosed()) {
clientSocket.close();
}
System.out.println("客户端连接已关闭");
} catch (IOException e) {
System.err.println("关闭连接时出错: " + e.getMessage());
} finally {
clientSocket = null;
inputStream = null;
outputStream = null;
}
}
}

View File

@ -70,7 +70,7 @@ public class SisElevatorInfoServiceImpl implements ISisElevatorInfoService {
SisElevatorInfoVo.DeviceInfo deviceInfo = new SisElevatorInfoVo.DeviceInfo();
deviceInfo.setDeviceId(item.getDeviceId());
deviceInfo.setDeviceIp(item.getDeviceIp());
if (Objects.equals(item.getControlType(), ControlTypeEnum.REMOTE_CALL_ELEVATOR.getCode())) {
if (Objects.equals(item.getControlType(), ControlTypeEnum.ELEVATOR_OUT_CONTROL.getCode())) {
remoteCallElevatorDeviceId.add(deviceInfo);
} else {
sisElevatorInfoVo.setElevatorControlDeviceId(deviceInfo);
@ -207,7 +207,7 @@ public class SisElevatorInfoServiceImpl implements ISisElevatorInfoService {
ref.setDeviceIp(bo.getElevatorControlDeviceId().getDeviceIp());
ref.setBindId(bo.getElevatorId());
ref.setDeviceFloorId(vo.getFloorId());
ref.setControlType(ControlTypeEnum.ELEVATOR_CONTROL.getCode());
ref.setControlType(ControlTypeEnum.ELEVATOR_IN_CONTROL.getCode());
ls.add(ref);
}
// 远程呼叫
@ -220,7 +220,7 @@ public class SisElevatorInfoServiceImpl implements ISisElevatorInfoService {
ref.setDeviceIp(deviceInfo.getDeviceIp());
ref.setBindId(bo.getElevatorId());
ref.setDeviceFloorId(vo.getFloorId());
ref.setControlType(ControlTypeEnum.REMOTE_CALL_ELEVATOR.getCode());
ref.setControlType(ControlTypeEnum.ELEVATOR_OUT_CONTROL.getCode());
ls.add(ref);
}
}

View File

@ -4,10 +4,13 @@ import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.property.api.RemoteFloorService;
import org.dromara.property.api.domain.vo.RemoteFloorVo;
import org.dromara.sis.domain.enums.ControlTypeEnum;
import org.dromara.sis.domain.enums.RosterTypeEnum;
import org.dromara.sis.domain.vo.*;
import org.dromara.sis.producer.CleanLiftAuthRocketProducer;
@ -16,9 +19,11 @@ import org.dromara.sis.sdk.e8.domain.accessControl.req.RemoteOpenDoorReq;
import org.dromara.sis.sdk.hik.HikApiService;
import org.dromara.sis.sdk.huawei.HuaWeiBoxApi;
import org.dromara.sis.sdk.huawei.domain.HWResult;
import org.dromara.sis.sdk.smartDevices.utils.ElevatorControlUtil;
import org.dromara.sis.service.*;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@ -58,7 +63,7 @@ public class ZeroSensationPassageServiceImpl implements IZeroSensationPassageSer
if (result.getCode() != 200) {
log.info("华为盒子比对失败,msg={}", result.getMessage());
// 产生告警数据
alarmEventsService.createAlarmRecord(deviceIp, 1, 1, "人脸比对失败", smallImg, bigImg);
// alarmEventsService.createAlarmRecord(deviceIp, 1, 1, "人脸比对失败", smallImg, bigImg);
return;
}
log.info("人脸比对执行完成,耗时:{}ms", interval.intervalMs());
@ -69,19 +74,19 @@ public class ZeroSensationPassageServiceImpl implements IZeroSensationPassageSer
if (authRecord == null) {
log.info("人员[{}]没有授权记录,判定为陌生人", person);
// 不是内部人员 产生紧急的告警信息
alarmEventsService.createAlarmRecord(deviceIp, 1, 1, "陌生人员入内", smallImg, bigImg);
// alarmEventsService.createAlarmRecord(deviceIp, 1, 1, "陌生人员入内", smallImg, bigImg);
return;
}else {
} else {
if (Objects.equals(authRecord.getRosterType(), RosterTypeEnum.BLACK_LIST.getCode())) {
log.info("人员[{}]在黑名单中,暂不处理。", person);
alarmEventsService.createAlarmRecord(deviceIp, 3, 1, "黑名单人员入内", smallImg, bigImg);
// alarmEventsService.createAlarmRecord(deviceIp, 3, 1, "黑名单人员入内", smallImg, bigImg);
return;
}
}
Date now = new Date();
if (DateUtil.compare(now, authRecord.getEndDate()) > 0) {
alarmEventsService.createAlarmRecord(deviceIp, 3, 1, "人员授权信息已过期", smallImg, bigImg);
// alarmEventsService.createAlarmRecord(deviceIp, 3, 1, "人员授权信息已过期", smallImg, bigImg);
log.info("当前人脸已过期,暂不处理。");
return;
}
@ -109,12 +114,12 @@ public class ZeroSensationPassageServiceImpl implements IZeroSensationPassageSer
return;
}
// 判断绑定设备类型走不同的处理方法
if (item.getControlType() == 1) { // 门禁
if (Objects.equals(item.getControlType(), ControlTypeEnum.ACCESS_CONTROL.getCode())) { // 门禁
handleAc(item.getDeviceId());
} else if (item.getControlType() == 2) { // 电梯外面面板权限
handleEle(item.getDeviceId(), r.getAuthGroupId(), 2, item.getDeviceFloorId());
} else if (item.getControlType() == 3) { // 电梯里面的面板
handleEle(item.getDeviceId(), r.getAuthGroupId(), 3, item.getDeviceFloorId());
} else if (item.getControlType().equals(ControlTypeEnum.ELEVATOR_OUT_CONTROL.getCode())) { // 电梯外面面板权限
handleEle(item.getDeviceId(), r.getAuthGroupId(), ControlTypeEnum.ELEVATOR_OUT_CONTROL.getCode(), item.getDeviceFloorId());
} else if (item.getControlType().equals(ControlTypeEnum.ELEVATOR_IN_CONTROL.getCode())) { // 电梯里面的面板
handleEle(item.getDeviceId(), r.getAuthGroupId(), ControlTypeEnum.ELEVATOR_IN_CONTROL.getCode(), item.getDeviceFloorId());
} else {
log.info("设备绑定了未知的控制类型[{}],不处理", item.getControlType());
}
@ -157,13 +162,15 @@ public class ZeroSensationPassageServiceImpl implements IZeroSensationPassageSer
// 获取权限组下电梯楼层关联信息
List<SisElevatorFloorRefVo> groupRef = elevatorFloorRefService.queryByAuthGroupId(groupId);
if (ObjectUtil.isEmpty(groupRef)) return;
// 取出当前电梯的楼层授权信息
List<SisElevatorFloorRefVo> eleRef = groupRef.stream().filter(o -> Objects.equals(o.getElevatorId(), deviceId)).toList();
if (ObjectUtil.isEmpty(eleRef)) return;
for (SisElevatorFloorRefVo ref : eleRef){
if (controlType == 2){
log.info("开始下发外面版梯控权限....");
if (Objects.equals(controlType, ControlTypeEnum.ELEVATOR_IN_CONTROL.getCode())) {
log.info("开始下发里面版梯控权限....");
for (SisElevatorFloorRefVo ref : eleRef) {
if (ref.getUpChannel() != null && Objects.equals(ref.getFloorId(), deviceFloorId)) {
HikApiService.getInstance().controlGateway(ele.getControlIp(), ref.getUpChannel().intValue(), 2);
}
@ -171,12 +178,33 @@ public class ZeroSensationPassageServiceImpl implements IZeroSensationPassageSer
if (ref.getDownChannel() != null && Objects.equals(ref.getFloorId(), deviceFloorId)) {
HikApiService.getInstance().controlGateway(ele.getControlIp(), ref.getDownChannel().intValue(), 2);
}
}else {
log.info("开始下发里面版梯控权限....");
if (ref.getInChannel() != null && Objects.equals(ref.getFloorId(), deviceFloorId)) {
HikApiService.getInstance().controlGateway(ele.getControlIp(), ref.getInChannel().intValue(), 2);
}
}
// 获取当前电梯所在建筑的楼层
List<RemoteFloorVo> floorList = remoteFloorService.queryByBuildingId(ele.getBuildingId());
if (CollUtil.isEmpty(floorList)) return;
if (Objects.equals(controlType, ControlTypeEnum.ELEVATOR_OUT_CONTROL.getCode())) {
SisElevatorFloorRefVo vo;
List<Integer> num = new ArrayList<>();
for (int i = 1; i < floorList.size(); i++) {
int finalI = i;
// 取出权限楼层id与实际楼层id相等的数据
vo = eleRef.stream().filter(o -> Objects.equals(o.getFloorId(), floorList.get(finalI-1).getId())).findFirst().orElse(null);
// 存在权限楼层添加到num中梯控模块从1开始
if (vo != null) {
num.add(i);
}
}
if (CollUtil.isEmpty(num)) return;
if (!ElevatorControlUtil.getInstance().isClientConnected()){
log.info("梯控模块未连接,请检查梯控模块是否启动");
return;
}
ElevatorControlUtil.getInstance().sendManualCommand(num);
}
log.info("梯控下发权限完成");