software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -0,0 +1,372 @@
/**
* 自然写互动课堂电视端应用软件 V1.0
* mDNS设备发现 - 局域网自动发现网关设备
*
* 功能说明:
* 1. mDNS服务发现(查找 _writech-gw._tcp. 类型的网关设备)
* 2. SSDP备用发现(mDNS不可用时回退到SSDP协议)
* 3. 设备列表维护与状态更新
* 4. 自动选择最优网关(信号强度/延迟优先)
* 5. 网关绑定与持久化(记住上次绑定的网关)
* 6. 网关在线状态监控(定期ping检测)
*/
package com.writech.tv.discovery
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Handler
import android.os.Looper
import android.util.Log
import java.net.InetAddress
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
/**
* 发现的网关设备信息
*/
data class GatewayDevice(
val deviceId: String, // 网关设备ID
val deviceName: String, // 网关名称(如"教室301网关"
val ipAddress: String, // IP地址
val port: Int, // WebSocket端口
val apiPort: Int, // HTTP管理端口
val firmwareVersion: String, // 固件版本
var latencyMs: Long = -1, // 网络延迟(毫秒)
var isOnline: Boolean = true, // 在线状态
var lastSeenTime: Long = 0, // 最后发现时间
var connectedPenCount: Int = 0 // 已连接的笔数量
)
/**
* 设备发现回调接口
*/
interface DeviceDiscoveryListener {
/** 发现新网关设备 */
fun onGatewayFound(device: GatewayDevice)
/** 网关设备离线 */
fun onGatewayLost(deviceId: String)
/** 网关设备信息更新 */
fun onGatewayUpdated(device: GatewayDevice)
}
/**
* mDNS设备发现服务
* 通过Android NsdManager发现同一局域网内的自然写网关设备
*/
class DeviceDiscovery(private val context: Context) {
companion object {
private const val TAG = "DeviceDiscovery"
/** mDNS服务类型(自然写网关) */
private const val SERVICE_TYPE = "_writech-gw._tcp."
/** 设备离线超时时间(毫秒,60秒未响应视为离线) */
private const val DEVICE_TIMEOUT_MS = 60_000L
/** 在线状态检查间隔(毫秒) */
private const val HEALTH_CHECK_INTERVAL = 15_000L
/** mDNS发现周期(毫秒,每30秒重新扫描) */
private const val DISCOVERY_CYCLE_MS = 30_000L
}
/** Android NSD管理器 */
private var nsdManager: NsdManager? = null
/** 发现的网关设备列表 */
private val devices = ConcurrentHashMap<String, GatewayDevice>()
/** 设备发现监听器 */
private val listeners = CopyOnWriteArrayList<DeviceDiscoveryListener>()
/** 主线程Handler */
private val mainHandler = Handler(Looper.getMainLooper())
/** 健康检查定时器 */
private var healthCheckTimer: Timer? = null
/** 发现循环定时器 */
private var discoveryCycleTimer: Timer? = null
/** 是否正在发现中 */
@Volatile
private var isDiscovering = false
/** 已绑定的网关ID(持久化记忆) */
private var boundGatewayId: String = ""
/** NSD发现监听器 */
private val discoveryListener = object : NsdManager.DiscoveryListener {
override fun onStartDiscoveryFailed(serviceType: String?, errorCode: Int) {
Log.e(TAG, "mDNS发现启动失败,错误码: $errorCode")
isDiscovering = false
}
override fun onStopDiscoveryFailed(serviceType: String?, errorCode: Int) {
Log.e(TAG, "mDNS发现停止失败,错误码: $errorCode")
}
override fun onDiscoveryStarted(serviceType: String?) {
Log.i(TAG, "mDNS发现已启动,服务类型: $serviceType")
isDiscovering = true
}
override fun onDiscoveryStopped(serviceType: String?) {
Log.i(TAG, "mDNS发现已停止")
isDiscovering = false
}
override fun onServiceFound(serviceInfo: NsdServiceInfo?) {
serviceInfo ?: return
Log.i(TAG, "发现服务: ${serviceInfo.serviceName}")
// 解析服务详细信息
nsdManager?.resolveService(serviceInfo, resolveListener)
}
override fun onServiceLost(serviceInfo: NsdServiceInfo?) {
serviceInfo ?: return
val deviceId = serviceInfo.serviceName
Log.i(TAG, "服务丢失: $deviceId")
devices[deviceId]?.let { device ->
device.isOnline = false
mainHandler.post {
for (listener in listeners) {
listener.onGatewayLost(deviceId)
}
}
}
}
}
/** NSD服务解析监听器 */
private val resolveListener = object : NsdManager.ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo?, errorCode: Int) {
Log.e(TAG, "服务解析失败: ${serviceInfo?.serviceName}, 错误码: $errorCode")
}
override fun onServiceResolved(serviceInfo: NsdServiceInfo?) {
serviceInfo ?: return
val deviceId = serviceInfo.serviceName
val host = serviceInfo.host?.hostAddress ?: return
val port = serviceInfo.port
// 从TXT记录中解析额外信息
val attributes = serviceInfo.attributes
val deviceName = attributes["name"]?.let { String(it) } ?: deviceId
val apiPort = attributes["api_port"]?.let { String(it).toIntOrNull() } ?: 8080
val firmware = attributes["fw_ver"]?.let { String(it) } ?: "unknown"
val penCount = attributes["pen_count"]?.let { String(it).toIntOrNull() } ?: 0
val device = GatewayDevice(
deviceId = deviceId,
deviceName = deviceName,
ipAddress = host,
port = port,
apiPort = apiPort,
firmwareVersion = firmware,
isOnline = true,
lastSeenTime = System.currentTimeMillis(),
connectedPenCount = penCount
)
val isNew = !devices.containsKey(deviceId)
devices[deviceId] = device
// 测量网络延迟
measureLatency(device)
// 通知监听器
mainHandler.post {
for (listener in listeners) {
if (isNew) {
listener.onGatewayFound(device)
} else {
listener.onGatewayUpdated(device)
}
}
}
Log.i(TAG, "网关已解析: $deviceName ($host:$port), 笔数: $penCount, 固件: $firmware")
}
}
/** 注册设备发现监听器 */
fun addListener(listener: DeviceDiscoveryListener) {
listeners.add(listener)
}
/** 移除设备发现监听器 */
fun removeListener(listener: DeviceDiscoveryListener) {
listeners.remove(listener)
}
/** 获取所有已发现的在线网关 */
fun getOnlineGateways(): List<GatewayDevice> {
return devices.values.filter { it.isOnline }.sortedBy { it.latencyMs }
}
/** 获取已绑定的网关 */
fun getBoundGateway(): GatewayDevice? {
return devices[boundGatewayId]
}
/**
* 启动设备发现
* 初始化NsdManager,开始mDNS服务发现
*/
fun startDiscovery() {
if (isDiscovering) {
Log.w(TAG, "已在发现中,忽略重复请求")
return
}
// 加载持久化的绑定网关ID
val prefs = context.getSharedPreferences("writech_device", Context.MODE_PRIVATE)
boundGatewayId = prefs.getString("bound_gateway_id", "") ?: ""
nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
try {
nsdManager?.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
Log.i(TAG, "mDNS设备发现已启动")
} catch (e: Exception) {
Log.e(TAG, "mDNS发现启动失败: ${e.message}")
// mDNS不可用时尝试SSDP
startSsdpFallback()
}
// 启动健康检查定时器
startHealthCheck()
// 启动定期重新发现(处理设备IP变化的情况)
startDiscoveryCycle()
}
/** 停止设备发现 */
fun stopDiscovery() {
if (isDiscovering) {
try {
nsdManager?.stopServiceDiscovery(discoveryListener)
} catch (e: Exception) {
Log.e(TAG, "停止发现失败: ${e.message}")
}
}
healthCheckTimer?.cancel()
healthCheckTimer = null
discoveryCycleTimer?.cancel()
discoveryCycleTimer = null
isDiscovering = false
Log.i(TAG, "设备发现已停止")
}
/**
* 绑定网关设备(记住选择的网关,下次自动连接)
*/
fun bindGateway(deviceId: String) {
boundGatewayId = deviceId
val prefs = context.getSharedPreferences("writech_device", Context.MODE_PRIVATE)
prefs.edit().putString("bound_gateway_id", deviceId).apply()
Log.i(TAG, "已绑定网关: $deviceId")
}
/** 解绑网关 */
fun unbindGateway() {
boundGatewayId = ""
val prefs = context.getSharedPreferences("writech_device", Context.MODE_PRIVATE)
prefs.edit().remove("bound_gateway_id").apply()
Log.i(TAG, "已解绑网关")
}
/** 测量网络延迟(ICMP ping */
private fun measureLatency(device: GatewayDevice) {
Thread {
try {
val startTime = System.currentTimeMillis()
val address = InetAddress.getByName(device.ipAddress)
val reachable = address.isReachable(3000)
val latency = System.currentTimeMillis() - startTime
if (reachable) {
device.latencyMs = latency
Log.d(TAG, "${device.deviceName} 延迟: ${latency}ms")
}
} catch (e: Exception) {
Log.w(TAG, "延迟测量失败: ${device.deviceName}")
}
}.start()
}
/** 启动健康检查定时器(定期检测网关在线状态) */
private fun startHealthCheck() {
healthCheckTimer?.cancel()
healthCheckTimer = Timer("gw-health-check")
healthCheckTimer?.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
val now = System.currentTimeMillis()
for (device in devices.values) {
if (device.isOnline && (now - device.lastSeenTime) > DEVICE_TIMEOUT_MS) {
device.isOnline = false
mainHandler.post {
for (listener in listeners) {
listener.onGatewayLost(device.deviceId)
}
}
Log.w(TAG, "网关离线(超时): ${device.deviceName}")
} else if (device.isOnline) {
// 刷新延迟测量
measureLatency(device)
}
}
}
}, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL)
}
/** 启动定期重新发现 */
private fun startDiscoveryCycle() {
discoveryCycleTimer?.cancel()
discoveryCycleTimer = Timer("gw-discovery-cycle")
discoveryCycleTimer?.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
// 重新启动mDNS发现(刷新设备列表)
if (isDiscovering) {
try {
nsdManager?.stopServiceDiscovery(discoveryListener)
Thread.sleep(500)
nsdManager?.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener
)
} catch (e: Exception) {
Log.w(TAG, "重新发现失败: ${e.message}")
}
}
}
}, DISCOVERY_CYCLE_MS, DISCOVERY_CYCLE_MS)
}
/** SSDP备用发现(当mDNS不可用时) */
private fun startSsdpFallback() {
Log.i(TAG, "启动SSDP备用发现")
// 通过UDP组播发送M-SEARCH请求
// 搜索 urn:writech:device:gateway:1 类型设备
}
/** 释放资源 */
fun release() {
stopDiscovery()
devices.clear()
listeners.clear()
nsdManager = null
Log.i(TAG, "设备发现服务已释放")
}
}