/* * 自然写互动课堂应用开发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 mDiscoveredGateways = new ConcurrentHashMap<>(); /* 已连接的网关WebSocket映射 */ private final Map mConnections = new ConcurrentHashMap<>(); /* 回调监听器 */ private final List mDiscoveryListeners = new CopyOnWriteArrayList<>(); private final List mConnectionListeners = new CopyOnWriteArrayList<>(); private final List 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 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 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资源已释放"); } }