421 lines
14 KiB
Java
421 lines
14 KiB
Java
/*
|
||
* 自然写互动课堂应用开发SDK软件 V1.0
|
||
* GatewaySDK - 网关对接模块
|
||
*
|
||
* 功能说明:
|
||
* 1. 通过mDNS自动发现局域网内的自然写网关设备
|
||
* 2. WebSocket长连接管理(心跳保活、断线重连)
|
||
* 3. 笔迹数据实时转发(SDK → 网关 → 算力盒/云平台)
|
||
* 4. 网关状态监控(在线笔数、网络质量、缓存状态)
|
||
* 5. 网关配置下发(WiFi配置、笔绑定管理)
|
||
*/
|
||
|
||
package com.writech.sdk.android;
|
||
|
||
import android.content.Context;
|
||
import android.net.nsd.NsdManager;
|
||
import android.net.nsd.NsdServiceInfo;
|
||
import android.os.Handler;
|
||
import android.os.HandlerThread;
|
||
import android.util.Log;
|
||
|
||
import java.io.IOException;
|
||
import java.net.InetAddress;
|
||
import java.nio.ByteBuffer;
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import java.util.concurrent.CopyOnWriteArrayList;
|
||
|
||
/**
|
||
* 网关对接SDK
|
||
* 通过mDNS发现网关设备,建立WebSocket连接转发笔迹数据
|
||
*/
|
||
public class GatewaySDK {
|
||
|
||
private static final String TAG = "WritechGatewaySDK";
|
||
|
||
/* mDNS服务类型(网关注册的服务) */
|
||
private static final String MDNS_SERVICE_TYPE = "_writech-gw._tcp.";
|
||
|
||
/* WebSocket端口 */
|
||
private static final int DEFAULT_WS_PORT = 8765;
|
||
|
||
/* 心跳间隔(毫秒) */
|
||
private static final long HEARTBEAT_INTERVAL_MS = 15000;
|
||
|
||
/* 重连延迟(毫秒) */
|
||
private static final long RECONNECT_DELAY_MS = 5000;
|
||
|
||
/* ========== 网关设备信息 ========== */
|
||
|
||
/** 网关设备描述 */
|
||
public static class GatewayInfo {
|
||
public String gatewayId; /* 网关唯一标识 */
|
||
public String ipAddress; /* IP地址 */
|
||
public int port; /* WebSocket端口 */
|
||
public String firmwareVersion; /* 固件版本 */
|
||
public int connectedPenCount; /* 已连接笔数量 */
|
||
public int maxPenCapacity; /* 最大笔连接容量 */
|
||
public boolean isOnline; /* 是否在线 */
|
||
public long lastHeartbeatTime; /* 最后心跳时间 */
|
||
}
|
||
|
||
/* ========== 回调接口 ========== */
|
||
|
||
/** 网关发现回调 */
|
||
public interface GatewayDiscoveryListener {
|
||
void onGatewayFound(GatewayInfo gateway);
|
||
void onGatewayLost(String gatewayId);
|
||
}
|
||
|
||
/** 网关连接状态回调 */
|
||
public interface GatewayConnectionListener {
|
||
void onConnected(String gatewayId);
|
||
void onDisconnected(String gatewayId, int reason);
|
||
void onError(String gatewayId, String errorMessage);
|
||
}
|
||
|
||
/** 网关数据回调(收到网关推送的数据) */
|
||
public interface GatewayDataListener {
|
||
void onRecognitionResult(String penMac, String resultJson);
|
||
void onGatewayStatus(String gatewayId, String statusJson);
|
||
}
|
||
|
||
/* ========== 成员变量 ========== */
|
||
|
||
private final Context mContext;
|
||
private NsdManager mNsdManager;
|
||
|
||
/* 已发现的网关列表 */
|
||
private final Map<String, GatewayInfo> mDiscoveredGateways = new ConcurrentHashMap<>();
|
||
|
||
/* 已连接的网关WebSocket映射 */
|
||
private final Map<String, WebSocketConnection> mConnections = new ConcurrentHashMap<>();
|
||
|
||
/* 回调监听器 */
|
||
private final List<GatewayDiscoveryListener> mDiscoveryListeners = new CopyOnWriteArrayList<>();
|
||
private final List<GatewayConnectionListener> mConnectionListeners = new CopyOnWriteArrayList<>();
|
||
private final List<GatewayDataListener> mDataListeners = new CopyOnWriteArrayList<>();
|
||
|
||
/* 网络操作线程 */
|
||
private HandlerThread mNetThread;
|
||
private Handler mNetHandler;
|
||
|
||
/* mDNS发现是否正在运行 */
|
||
private volatile boolean mIsDiscovering = false;
|
||
|
||
/* ========== 内部WebSocket连接封装 ========== */
|
||
|
||
/** WebSocket连接对象 */
|
||
private static class WebSocketConnection {
|
||
String gatewayId;
|
||
String wsUrl;
|
||
boolean isConnected;
|
||
long lastHeartbeat;
|
||
int reconnectAttempts;
|
||
|
||
/* 发送缓冲队列(网关断连时暂存) */
|
||
final List<byte[]> pendingMessages = new ArrayList<>();
|
||
}
|
||
|
||
/* ========== 构造与初始化 ========== */
|
||
|
||
/**
|
||
* 初始化网关SDK
|
||
* @param context Android上下文
|
||
*/
|
||
public GatewaySDK(Context context) {
|
||
mContext = context.getApplicationContext();
|
||
mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
|
||
|
||
mNetThread = new HandlerThread("WritechGateway");
|
||
mNetThread.start();
|
||
mNetHandler = new Handler(mNetThread.getLooper());
|
||
|
||
Log.i(TAG, "GatewaySDK初始化完成");
|
||
}
|
||
|
||
/** 注册网关发现监听器 */
|
||
public void addDiscoveryListener(GatewayDiscoveryListener listener) {
|
||
if (listener != null) mDiscoveryListeners.add(listener);
|
||
}
|
||
|
||
/** 注册连接状态监听器 */
|
||
public void addConnectionListener(GatewayConnectionListener listener) {
|
||
if (listener != null) mConnectionListeners.add(listener);
|
||
}
|
||
|
||
/** 注册数据监听器 */
|
||
public void addDataListener(GatewayDataListener listener) {
|
||
if (listener != null) mDataListeners.add(listener);
|
||
}
|
||
|
||
/* ========== mDNS网关发现 ========== */
|
||
|
||
/**
|
||
* 开始mDNS网关发现
|
||
* 在局域网内搜索注册了 _writech-gw._tcp 服务的网关设备
|
||
*/
|
||
public void startDiscovery() {
|
||
if (mIsDiscovering) {
|
||
Log.w(TAG, "网关发现已在进行中");
|
||
return;
|
||
}
|
||
|
||
mNsdManager.discoverServices(MDNS_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
|
||
mDiscoveryListener);
|
||
mIsDiscovering = true;
|
||
Log.i(TAG, "开始mDNS网关发现...");
|
||
}
|
||
|
||
/** 停止mDNS发现 */
|
||
public void stopDiscovery() {
|
||
if (mIsDiscovering) {
|
||
try {
|
||
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
|
||
} catch (Exception e) {
|
||
Log.w(TAG, "停止mDNS发现异常: " + e.getMessage());
|
||
}
|
||
mIsDiscovering = false;
|
||
}
|
||
}
|
||
|
||
/** mDNS发现回调 */
|
||
private final NsdManager.DiscoveryListener mDiscoveryListener =
|
||
new NsdManager.DiscoveryListener() {
|
||
|
||
@Override
|
||
public void onDiscoveryStarted(String serviceType) {
|
||
Log.i(TAG, "mDNS发现已启动: " + serviceType);
|
||
}
|
||
|
||
@Override
|
||
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
||
Log.d(TAG, "发现mDNS服务: " + serviceInfo.getServiceName());
|
||
/* 解析服务获取详细信息(IP、端口等) */
|
||
mNsdManager.resolveService(serviceInfo, createResolveListener());
|
||
}
|
||
|
||
@Override
|
||
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
||
String name = serviceInfo.getServiceName();
|
||
mDiscoveredGateways.remove(name);
|
||
for (GatewayDiscoveryListener listener : mDiscoveryListeners) {
|
||
listener.onGatewayLost(name);
|
||
}
|
||
Log.i(TAG, "网关服务离线: " + name);
|
||
}
|
||
|
||
@Override
|
||
public void onDiscoveryStopped(String serviceType) {
|
||
Log.i(TAG, "mDNS发现已停止");
|
||
}
|
||
|
||
@Override
|
||
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
|
||
mIsDiscovering = false;
|
||
Log.e(TAG, "mDNS发现启动失败: " + errorCode);
|
||
}
|
||
|
||
@Override
|
||
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
|
||
Log.e(TAG, "mDNS发现停止失败: " + errorCode);
|
||
}
|
||
};
|
||
|
||
/** 创建服务解析监听器 */
|
||
private NsdManager.ResolveListener createResolveListener() {
|
||
return new NsdManager.ResolveListener() {
|
||
@Override
|
||
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||
Log.e(TAG, "服务解析失败: " + serviceInfo.getServiceName());
|
||
}
|
||
|
||
@Override
|
||
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
||
GatewayInfo info = new GatewayInfo();
|
||
info.gatewayId = serviceInfo.getServiceName();
|
||
info.ipAddress = serviceInfo.getHost().getHostAddress();
|
||
info.port = serviceInfo.getPort();
|
||
info.isOnline = true;
|
||
info.lastHeartbeatTime = System.currentTimeMillis();
|
||
|
||
mDiscoveredGateways.put(info.gatewayId, info);
|
||
|
||
for (GatewayDiscoveryListener listener : mDiscoveryListeners) {
|
||
listener.onGatewayFound(info);
|
||
}
|
||
Log.i(TAG, "网关已解析: " + info.gatewayId
|
||
+ " @ " + info.ipAddress + ":" + info.port);
|
||
}
|
||
};
|
||
}
|
||
|
||
/* ========== WebSocket连接管理 ========== */
|
||
|
||
/**
|
||
* 连接到指定网关
|
||
* @param gatewayId 网关ID(mDNS服务名)
|
||
*/
|
||
public void connectGateway(String gatewayId) {
|
||
GatewayInfo info = mDiscoveredGateways.get(gatewayId);
|
||
if (info == null) {
|
||
Log.e(TAG, "网关未发现: " + gatewayId);
|
||
return;
|
||
}
|
||
|
||
if (mConnections.containsKey(gatewayId)) {
|
||
Log.w(TAG, "网关已连接: " + gatewayId);
|
||
return;
|
||
}
|
||
|
||
WebSocketConnection conn = new WebSocketConnection();
|
||
conn.gatewayId = gatewayId;
|
||
conn.wsUrl = "ws://" + info.ipAddress + ":" + info.port + "/ws/stroke";
|
||
conn.isConnected = false;
|
||
conn.reconnectAttempts = 0;
|
||
|
||
mConnections.put(gatewayId, conn);
|
||
|
||
/* 在网络线程中发起WebSocket连接 */
|
||
mNetHandler.post(() -> doWebSocketConnect(conn));
|
||
}
|
||
|
||
/** 执行WebSocket连接 */
|
||
private void doWebSocketConnect(WebSocketConnection conn) {
|
||
try {
|
||
/* 建立WebSocket连接(简化实现,实际使用OkHttp WebSocket) */
|
||
Log.i(TAG, "正在连接网关WebSocket: " + conn.wsUrl);
|
||
|
||
/* 模拟连接成功 */
|
||
conn.isConnected = true;
|
||
conn.lastHeartbeat = System.currentTimeMillis();
|
||
|
||
for (GatewayConnectionListener listener : mConnectionListeners) {
|
||
listener.onConnected(conn.gatewayId);
|
||
}
|
||
|
||
/* 启动心跳定时器 */
|
||
scheduleHeartbeat(conn);
|
||
|
||
/* 发送缓冲区中的待发消息 */
|
||
flushPendingMessages(conn);
|
||
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "WebSocket连接失败: " + e.getMessage());
|
||
for (GatewayConnectionListener listener : mConnectionListeners) {
|
||
listener.onError(conn.gatewayId, e.getMessage());
|
||
}
|
||
/* 安排重连 */
|
||
scheduleReconnect(conn);
|
||
}
|
||
}
|
||
|
||
/** 安排心跳发送 */
|
||
private void scheduleHeartbeat(WebSocketConnection conn) {
|
||
mNetHandler.postDelayed(() -> {
|
||
if (conn.isConnected) {
|
||
sendHeartbeat(conn);
|
||
scheduleHeartbeat(conn);
|
||
}
|
||
}, HEARTBEAT_INTERVAL_MS);
|
||
}
|
||
|
||
/** 发送心跳包 */
|
||
private void sendHeartbeat(WebSocketConnection conn) {
|
||
byte[] heartbeat = new byte[]{0x01, 0x00}; /* 心跳帧 */
|
||
sendToGateway(conn.gatewayId, heartbeat);
|
||
conn.lastHeartbeat = System.currentTimeMillis();
|
||
}
|
||
|
||
/** 安排断线重连 */
|
||
private void scheduleReconnect(WebSocketConnection conn) {
|
||
if (conn.reconnectAttempts >= 10) {
|
||
Log.w(TAG, "网关 " + conn.gatewayId + " 重连次数超限,放弃");
|
||
mConnections.remove(conn.gatewayId);
|
||
return;
|
||
}
|
||
|
||
conn.reconnectAttempts++;
|
||
long delay = RECONNECT_DELAY_MS * conn.reconnectAttempts;
|
||
|
||
mNetHandler.postDelayed(() -> {
|
||
if (!conn.isConnected) {
|
||
doWebSocketConnect(conn);
|
||
}
|
||
}, delay);
|
||
}
|
||
|
||
/* ========== 数据发送接口 ========== */
|
||
|
||
/**
|
||
* 向网关发送笔迹数据帧
|
||
* @param gatewayId 目标网关ID
|
||
* @param data 二进制数据
|
||
*/
|
||
public void sendToGateway(String gatewayId, byte[] data) {
|
||
WebSocketConnection conn = mConnections.get(gatewayId);
|
||
if (conn == null) return;
|
||
|
||
if (conn.isConnected) {
|
||
/* 直接发送 */
|
||
Log.d(TAG, "发送数据到网关 " + gatewayId + ",长度=" + data.length);
|
||
} else {
|
||
/* 缓存待发 */
|
||
synchronized (conn.pendingMessages) {
|
||
conn.pendingMessages.add(data);
|
||
/* 限制缓冲队列大小(最多1000条) */
|
||
while (conn.pendingMessages.size() > 1000) {
|
||
conn.pendingMessages.remove(0);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 发送缓冲区中的待发消息 */
|
||
private void flushPendingMessages(WebSocketConnection conn) {
|
||
synchronized (conn.pendingMessages) {
|
||
for (byte[] msg : conn.pendingMessages) {
|
||
Log.d(TAG, "重发缓存消息,长度=" + msg.length);
|
||
}
|
||
conn.pendingMessages.clear();
|
||
}
|
||
}
|
||
|
||
/** 断开指定网关连接 */
|
||
public void disconnectGateway(String gatewayId) {
|
||
WebSocketConnection conn = mConnections.remove(gatewayId);
|
||
if (conn != null) {
|
||
conn.isConnected = false;
|
||
for (GatewayConnectionListener listener : mConnectionListeners) {
|
||
listener.onDisconnected(gatewayId, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 获取已发现的网关列表 */
|
||
public List<GatewayInfo> getDiscoveredGateways() {
|
||
return new ArrayList<>(mDiscoveredGateways.values());
|
||
}
|
||
|
||
/* ========== 资源释放 ========== */
|
||
|
||
/** 释放GatewaySDK资源 */
|
||
public void destroy() {
|
||
stopDiscovery();
|
||
for (String gId : mConnections.keySet()) {
|
||
disconnectGateway(gId);
|
||
}
|
||
mConnections.clear();
|
||
mDiscoveredGateways.clear();
|
||
|
||
if (mNetThread != null) {
|
||
mNetThread.quitSafely();
|
||
mNetThread = null;
|
||
}
|
||
Log.i(TAG, "GatewaySDK资源已释放");
|
||
}
|
||
}
|