You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
618 lines
21 KiB
618 lines
21 KiB
package com.ouxuan.oxface.device;
|
|
|
|
import android.content.Context;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.util.Log;
|
|
|
|
import java.net.DatagramPacket;
|
|
import java.net.DatagramSocket;
|
|
import java.net.InetAddress;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
import java.util.concurrent.ScheduledFuture;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* 门禁UDP通信控制类
|
|
* 用于控制AB门的开关状态查询和操作
|
|
*
|
|
* 功能特性:
|
|
* - 支持设备信息查询和更新
|
|
* - 支持门禁状态轮询
|
|
* - 支持A门、B门开门控制
|
|
* - 支持门关闭时间设置
|
|
*
|
|
* @author AI Assistant
|
|
* @version 1.0
|
|
*/
|
|
public class OxUDP {
|
|
|
|
private static final String TAG = "OxUDP";
|
|
|
|
// UDP配置参数
|
|
private static final String UDP_IP = "192.168.1.123"; // MJ AB中控-所有设备通用
|
|
private static final int UDP_SEND_PORT = 60000; // 发送端口
|
|
private static final int POLLING_INTERVAL = 5000; // 轮询间隔(毫秒)
|
|
private static final int UDP_TIMEOUT = 3000; // UDP超时时间(毫秒)
|
|
|
|
// UDP命令定义
|
|
private String udp_device_cmd = "17 94 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
|
private String udp_state_cmd = "17 20 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
|
private String udp_open_a_cmd = "17 40 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
|
private String udp_open_b_cmd = "17 40 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
|
private String udp_gate_a_close_time_cmd = "17 80 00 00 3E 4F 18 0F 01 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
|
|
|
// 状态变量
|
|
private String udp_device_res = ""; // 设备返回的数据
|
|
private int udp_gate_a_close_time = 2; // A门关闭时间(秒)
|
|
private boolean isInitialized = false;
|
|
private boolean isPolling = false;
|
|
|
|
// 线程管理
|
|
private ExecutorService executorService;
|
|
private ScheduledExecutorService scheduledExecutor;
|
|
private ScheduledFuture<?> pollingTask;
|
|
private Handler mainHandler;
|
|
|
|
// 单例实例
|
|
private static OxUDP instance;
|
|
private Context context;
|
|
|
|
// 回调接口
|
|
private UDPStateListener stateListener;
|
|
private UDPDeviceListener deviceListener;
|
|
|
|
/**
|
|
* UDP状态监听接口
|
|
*/
|
|
public interface UDPStateListener {
|
|
/**
|
|
* 门禁状态更新
|
|
* @param gateAState A门状态 (true: 开启, false: 关闭)
|
|
* @param gateBState B门状态 (true: 开启, false: 关闭)
|
|
* @param rawData 原始返回数据
|
|
*/
|
|
void onGateStateUpdate(boolean gateAState, boolean gateBState, String rawData);
|
|
|
|
/**
|
|
* 门开启成功回调
|
|
* @param gateType 门类型 ("A" 或 "B")
|
|
* @param success 是否成功
|
|
*/
|
|
void onGateOpenResult(String gateType, boolean success);
|
|
|
|
/**
|
|
* UDP通信错误
|
|
* @param error 错误信息
|
|
*/
|
|
void onUDPError(String error);
|
|
}
|
|
|
|
/**
|
|
* UDP设备监听接口
|
|
*/
|
|
public interface UDPDeviceListener {
|
|
/**
|
|
* 设备信息更新
|
|
* @param deviceInfo 设备信息
|
|
*/
|
|
void onDeviceInfoUpdate(String deviceInfo);
|
|
|
|
/**
|
|
* 设备连接状态变化
|
|
* @param connected 是否连接
|
|
*/
|
|
void onDeviceConnectionChange(boolean connected);
|
|
}
|
|
|
|
private OxUDP() {
|
|
executorService = Executors.newCachedThreadPool();
|
|
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
|
mainHandler = new Handler(Looper.getMainLooper());
|
|
}
|
|
|
|
/**
|
|
* 获取单例实例
|
|
*/
|
|
public static synchronized OxUDP getInstance() {
|
|
if (instance == null) {
|
|
instance = new OxUDP();
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* 初始化UDP连接
|
|
* @param context 应用上下文
|
|
*/
|
|
public void initUDP(Context context) {
|
|
this.context = context.getApplicationContext();
|
|
|
|
Log.i(TAG, "开始初始化UDP连接...");
|
|
|
|
executorService.execute(() -> {
|
|
try {
|
|
// 更新设备信息
|
|
updateDeviceUDP();
|
|
isInitialized = true;
|
|
|
|
Log.i(TAG, "UDP初始化成功");
|
|
notifyDeviceConnectionChange(true);
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "UDP初始化失败", e);
|
|
notifyUDPError("UDP初始化失败: " + e.getMessage());
|
|
isInitialized = false;
|
|
notifyDeviceConnectionChange(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新当前连接UDP的设备信息到命令中
|
|
*/
|
|
private void updateDeviceUDP() {
|
|
Log.d(TAG, "开始更新设备UDP信息...");
|
|
|
|
try {
|
|
// 发送设备查询命令
|
|
String response = sendUDPCommand(udp_device_cmd);
|
|
|
|
if (response != null && !response.isEmpty()) {
|
|
Log.d(TAG, "updateDeviceUDP 响应: " + response);
|
|
|
|
// 更新设备返回的数据
|
|
udp_device_res = response;
|
|
|
|
// 更新各种命令的设备信息
|
|
udp_state_cmd = updateCmdDeviceInfo(response, udp_state_cmd);
|
|
udp_open_a_cmd = updateCmdDeviceInfo(response, udp_open_a_cmd);
|
|
udp_open_b_cmd = updateCmdDeviceInfo(response, udp_open_b_cmd);
|
|
udp_gate_a_close_time_cmd = updateCmdDeviceInfo(response, udp_gate_a_close_time_cmd);
|
|
|
|
Log.i(TAG, "设备信息更新成功");
|
|
notifyDeviceInfoUpdate(response);
|
|
|
|
} else {
|
|
Log.w(TAG, "设备返回数据为空");
|
|
notifyUDPError("设备返回数据为空");
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "更新设备UDP信息失败", e);
|
|
notifyUDPError("更新设备信息失败: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新命令中的设备信息
|
|
* @param deviceRes 设备返回的数据
|
|
* @param cmd 要更新的命令
|
|
* @return 更新后的命令
|
|
*/
|
|
private String updateCmdDeviceInfo(String deviceRes, String cmd) {
|
|
try {
|
|
String[] resDeviceArr = getCmdArr(deviceRes);
|
|
String[] cmdArr = getCmdArr(cmd);
|
|
|
|
// 替换cmd中的4-7位(索引3-6)
|
|
for (int i = 4; i <= 7; i++) {
|
|
if (i < resDeviceArr.length && i < cmdArr.length) {
|
|
cmdArr[i] = resDeviceArr[i];
|
|
}
|
|
}
|
|
|
|
// 重新组合命令
|
|
StringBuilder result = new StringBuilder();
|
|
for (int i = 0; i < cmdArr.length; i++) {
|
|
if (i > 0) result.append(" ");
|
|
result.append(cmdArr[i]);
|
|
}
|
|
|
|
String updatedCmd = result.toString();
|
|
Log.d(TAG, "updateCmdDeviceInfo: " + updatedCmd);
|
|
|
|
return updatedCmd;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "更新命令设备信息失败", e);
|
|
return cmd; // 返回原命令
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 将命令字符串转换为数组
|
|
* @param cmd 命令字符串(如 "17 94 00 00 ...")
|
|
* @return 命令数组
|
|
*/
|
|
private String[] getCmdArr(String cmd) {
|
|
return cmd.trim().split("\\s+");
|
|
}
|
|
|
|
/**
|
|
* 将命令字符串转换为字节数组
|
|
* @param cmd 命令字符串(如 "17 94 00 00 ...")
|
|
* @return 字节数组
|
|
*/
|
|
private byte[] getCmdBytes(String cmd) {
|
|
String[] hexStrings = getCmdArr(cmd);
|
|
byte[] bytes = new byte[hexStrings.length];
|
|
|
|
for (int i = 0; i < hexStrings.length; i++) {
|
|
try {
|
|
bytes[i] = (byte) Integer.parseInt(hexStrings[i], 16);
|
|
} catch (NumberFormatException e) {
|
|
Log.e(TAG, "解析十六进制字符串失败: " + hexStrings[i], e);
|
|
bytes[i] = 0;
|
|
}
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
/**
|
|
* 将字节数组转换为十六进制字符串
|
|
* @param bytes 字节数组
|
|
* @return 十六进制字符串(如 "17 94 00 00 ...")
|
|
*/
|
|
private String bytesToHexString(byte[] bytes) {
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < bytes.length; i++) {
|
|
if (i > 0) sb.append(" ");
|
|
sb.append(String.format("%02X", bytes[i] & 0xFF));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* 发送UDP命令并获取响应
|
|
* @param command 要发送的命令
|
|
* @return 响应数据
|
|
*/
|
|
private String sendUDPCommand(String command) {
|
|
DatagramSocket socket = null;
|
|
try {
|
|
// 创建UDP套接字
|
|
socket = new DatagramSocket();
|
|
socket.setSoTimeout(UDP_TIMEOUT);
|
|
|
|
// 准备发送数据
|
|
byte[] sendData = getCmdBytes(command);
|
|
InetAddress serverAddress = InetAddress.getByName(UDP_IP);
|
|
DatagramPacket sendPacket = new DatagramPacket(
|
|
sendData, sendData.length, serverAddress, UDP_SEND_PORT);
|
|
|
|
// Log.d(TAG, "发送UDP命令到 " + UDP_IP + ":" + UDP_SEND_PORT + " - " + command);
|
|
|
|
// 发送数据
|
|
socket.send(sendPacket);
|
|
|
|
// 接收响应
|
|
byte[] receiveData = new byte[1024];
|
|
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
|
|
socket.receive(receivePacket);
|
|
|
|
// 解析响应
|
|
byte[] responseBytes = new byte[receivePacket.getLength()];
|
|
System.arraycopy(receiveData, 0, responseBytes, 0, receivePacket.getLength());
|
|
String response = bytesToHexString(responseBytes);
|
|
|
|
// Log.d(TAG, "收到UDP响应: " + response);
|
|
return response;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "发送UDP命令失败: " + command, e);
|
|
return null;
|
|
} finally {
|
|
if (socket != null && !socket.isClosed()) {
|
|
socket.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 开始门禁状态轮询
|
|
*/
|
|
public void startPolling() {
|
|
if (!isInitialized) {
|
|
Log.w(TAG, "UDP未初始化,无法开始轮询");
|
|
return;
|
|
}
|
|
|
|
if (isPolling) {
|
|
Log.w(TAG, "轮询已在进行中");
|
|
return;
|
|
}
|
|
|
|
Log.i(TAG, "开始门禁状态轮询,间隔: " + POLLING_INTERVAL + "ms");
|
|
isPolling = true;
|
|
|
|
pollingTask = scheduledExecutor.scheduleWithFixedDelay(() -> {
|
|
try {
|
|
queryGateState();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "轮询过程中发生错误", e);
|
|
}
|
|
}, 0, POLLING_INTERVAL, TimeUnit.MILLISECONDS);
|
|
}
|
|
|
|
/**
|
|
* 停止门禁状态轮询
|
|
*/
|
|
public void stopPolling() {
|
|
if (!isPolling) {
|
|
return;
|
|
}
|
|
|
|
Log.i(TAG, "停止门禁状态轮询");
|
|
isPolling = false;
|
|
|
|
if (pollingTask != null && !pollingTask.isCancelled()) {
|
|
pollingTask.cancel(true);
|
|
pollingTask = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 查询门禁状态
|
|
*/
|
|
public void queryGateState() {
|
|
try {
|
|
String response = sendUDPCommand(udp_state_cmd);
|
|
|
|
if (response != null && !response.isEmpty()) {
|
|
// 解析门禁状态
|
|
parseGateState(response);
|
|
} else {
|
|
Log.w(TAG, "查询门禁状态失败,响应为空");
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "查询门禁状态失败", e);
|
|
notifyUDPError("查询门禁状态失败: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 解析门禁状态
|
|
* @param response 响应数据
|
|
*/
|
|
private void parseGateState(String response) {
|
|
try {
|
|
String[] responseArr = getCmdArr(response);
|
|
|
|
// 检查响应数据长度,确保包含门禁状态信息
|
|
if (responseArr.length < 30) {
|
|
Log.w(TAG, "响应数据长度不足,无法解析门禁状态。长度: " + responseArr.length);
|
|
notifyUDPError("响应数据长度不足: " + responseArr.length);
|
|
return;
|
|
}
|
|
|
|
// 根据协议解析门禁状态
|
|
// 第28位表示A门状态(数组索引28),第29位表示B门状态(数组索引29)
|
|
boolean gateAState = false;
|
|
boolean gateBState = false;
|
|
|
|
// 解析A门状态(第28位,数组索引28)
|
|
if (responseArr.length > 28) {
|
|
String aStateValue = responseArr[28];
|
|
gateAState = "01".equals(aStateValue); // 01表示开启,00表示关闭
|
|
Log.d(TAG, "A门状态值: " + aStateValue + " -> " + (gateAState ? "开启" : "关闭"));
|
|
}
|
|
|
|
// 解析B门状态(第29位,数组索引29)
|
|
if (responseArr.length > 29) {
|
|
String bStateValue = responseArr[29];
|
|
gateBState = "01".equals(bStateValue); // 01表示开启,00表示关闭
|
|
Log.d(TAG, "B门状态值: " + bStateValue + " -> " + (gateBState ? "开启" : "关闭"));
|
|
}
|
|
|
|
// 记录完整的响应数据用于调试
|
|
// Log.d(TAG, "完整UDP响应: " + response);
|
|
// Log.d(TAG, "响应数组长度: " + responseArr.length);
|
|
if (responseArr.length > 28) {
|
|
// Log.d(TAG, "第28位(A门): " + responseArr[28]);
|
|
}
|
|
if (responseArr.length > 29) {
|
|
// Log.d(TAG, "第29位(B门): " + responseArr[29]);
|
|
}
|
|
|
|
// Log.d(TAG, "门禁状态 - A门: " + (gateAState ? "开启" : "关闭") +
|
|
// ", B门: " + (gateBState ? "开启" : "关闭"));
|
|
|
|
notifyGateStateUpdate(gateAState, gateBState, response);
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "解析门禁状态失败", e);
|
|
notifyUDPError("解析门禁状态失败: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 打开A门(进门)
|
|
*/
|
|
public void openGateA() {
|
|
Log.i(TAG, "执行开启A门命令");
|
|
|
|
executorService.execute(() -> {
|
|
try {
|
|
String response = sendUDPCommand(udp_open_a_cmd);
|
|
boolean success = response != null && !response.isEmpty();
|
|
|
|
Log.i(TAG, "开启A门结果: " + (success ? "成功" : "失败"));
|
|
notifyGateOpenResult("A", success);
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "开启A门失败", e);
|
|
notifyGateOpenResult("A", false);
|
|
notifyUDPError("开启A门失败: " + e.getMessage());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 打开B门(出门)
|
|
*/
|
|
public void openGateB() {
|
|
Log.i(TAG, "执行开启B门命令");
|
|
|
|
executorService.execute(() -> {
|
|
try {
|
|
String response = sendUDPCommand(udp_open_b_cmd);
|
|
boolean success = response != null && !response.isEmpty();
|
|
|
|
Log.i(TAG, "开启B门结果: " + (success ? "成功" : "失败"));
|
|
notifyGateOpenResult("B", success);
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "开启B门失败", e);
|
|
notifyGateOpenResult("B", false);
|
|
notifyUDPError("开启B门失败: " + e.getMessage());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 设置A门关闭时间
|
|
* @param seconds 关闭时间(秒)
|
|
*/
|
|
public void setGateACloseTime(int seconds) {
|
|
this.udp_gate_a_close_time = seconds;
|
|
|
|
Log.i(TAG, "设置A门关闭时间: " + seconds + "秒");
|
|
|
|
executorService.execute(() -> {
|
|
try {
|
|
// 更新命令中的时间参数(需要根据实际协议调整)
|
|
// 这里假设第10位是时间参数
|
|
String[] cmdArr = getCmdArr(udp_gate_a_close_time_cmd);
|
|
if (cmdArr.length > 10) {
|
|
cmdArr[10] = String.format("%02X", seconds);
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < cmdArr.length; i++) {
|
|
if (i > 0) sb.append(" ");
|
|
sb.append(cmdArr[i]);
|
|
}
|
|
udp_gate_a_close_time_cmd = sb.toString();
|
|
}
|
|
|
|
String response = sendUDPCommand(udp_gate_a_close_time_cmd);
|
|
boolean success = response != null && !response.isEmpty();
|
|
|
|
Log.i(TAG, "设置A门关闭时间结果: " + (success ? "成功" : "失败"));
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "设置A门关闭时间失败", e);
|
|
notifyUDPError("设置A门关闭时间失败: " + e.getMessage());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 设置状态监听器
|
|
*/
|
|
public void setStateListener(UDPStateListener listener) {
|
|
this.stateListener = listener;
|
|
}
|
|
|
|
/**
|
|
* 设置设备监听器
|
|
*/
|
|
public void setDeviceListener(UDPDeviceListener listener) {
|
|
this.deviceListener = listener;
|
|
}
|
|
|
|
/**
|
|
* 获取初始化状态
|
|
*/
|
|
public boolean isInitialized() {
|
|
return isInitialized;
|
|
}
|
|
|
|
/**
|
|
* 获取轮询状态
|
|
*/
|
|
public boolean isPolling() {
|
|
return isPolling;
|
|
}
|
|
|
|
/**
|
|
* 获取设备返回数据
|
|
*/
|
|
public String getDeviceResponse() {
|
|
return udp_device_res;
|
|
}
|
|
|
|
/**
|
|
* 获取A门关闭时间
|
|
*/
|
|
public int getGateACloseTime() {
|
|
return udp_gate_a_close_time;
|
|
}
|
|
|
|
/**
|
|
* 停止UDP服务
|
|
*/
|
|
public void stop() {
|
|
Log.i(TAG, "停止UDP服务");
|
|
|
|
stopPolling();
|
|
isInitialized = false;
|
|
|
|
notifyDeviceConnectionChange(false);
|
|
}
|
|
|
|
/**
|
|
* 释放资源
|
|
*/
|
|
public void release() {
|
|
Log.i(TAG, "释放UDP资源");
|
|
|
|
stop();
|
|
|
|
if (executorService != null && !executorService.isShutdown()) {
|
|
executorService.shutdown();
|
|
}
|
|
|
|
if (scheduledExecutor != null && !scheduledExecutor.isShutdown()) {
|
|
scheduledExecutor.shutdown();
|
|
}
|
|
|
|
stateListener = null;
|
|
deviceListener = null;
|
|
}
|
|
|
|
// 通知方法
|
|
private void notifyGateStateUpdate(boolean gateAState, boolean gateBState, String rawData) {
|
|
if (stateListener != null) {
|
|
mainHandler.post(() -> stateListener.onGateStateUpdate(gateAState, gateBState, rawData));
|
|
}
|
|
}
|
|
|
|
private void notifyGateOpenResult(String gateType, boolean success) {
|
|
if (stateListener != null) {
|
|
mainHandler.post(() -> stateListener.onGateOpenResult(gateType, success));
|
|
}
|
|
}
|
|
|
|
private void notifyUDPError(String error) {
|
|
if (stateListener != null) {
|
|
mainHandler.post(() -> stateListener.onUDPError(error));
|
|
}
|
|
}
|
|
|
|
private void notifyDeviceInfoUpdate(String deviceInfo) {
|
|
if (deviceListener != null) {
|
|
mainHandler.post(() -> deviceListener.onDeviceInfoUpdate(deviceInfo));
|
|
}
|
|
}
|
|
|
|
private void notifyDeviceConnectionChange(boolean connected) {
|
|
if (deviceListener != null) {
|
|
mainHandler.post(() -> deviceListener.onDeviceConnectionChange(connected));
|
|
}
|
|
}
|
|
}
|