package com.example.smarthome.data import android.content.Context import android.content.SharedPreferences import android.util.Log import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import org.json.JSONObject import java.security.MessageDigest import java.security.NoSuchAlgorithmException /** * MQTT 服务 * * 依赖库(需要在 build.gradle 中添加): * implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5") * implementation("org.eclipse.paho:org.eclipse.paho.android.service:1.1.1") * * AndroidManifest.xml 需要添加: * */ // ==================== 常量定义(与原代码一致)==================== object MqttConstants { const val TAG = "MqttService" // 默认服务器配置 const val HOST_DOMAIN = "mqtt.cd-iot.cn" const val HOST_DOMAIN_API = "https://www.cd-iot.cn" const val HOST_PORT = ":1884" // 主题前缀 const val PUBLISH_HEAD = "bcup/0jnqd7slms9vvaf8/" const val PUBLISH_TAIL_EVENT = "/event" const val PUBLISH_TAIL_PROP = "/prop" const val PUBLISH_TAIL_UPDATEREPLY = "/upgrade/reply" const val PUBLISH_TAIL_MESSAGEREPLY = "/message/reply" const val SUBSCRIBE_HEAD = "bcdown/0jnqd7slms9vvaf8/" const val SUBSCRIBE_TAIL_UPDATE = "/upgrade/set" const val SUBSCRIBE_TAIL_PROPERTYSET = "/property/set" const val SUBSCRIBE_TAIL_SERVER = "/service/invoke" // QoS 等级 const val QOS_DEFAULT = 2 } // ==================== 数据模型 ==================== /** * MQTT 连接状态 */ enum class MqttConnectionState { DISCONNECTED, CONNECTING, CONNECTED, CONNECTION_LOST } /** * 设备信息(与原代码 Door 实体对应) */ data class DoorInfo( val cellName: String = "", // 格口名称 val cabinetName: String = "", // 柜子名称 val cellState: String = "", // 格口状态 val cellVoltageType: String = "", // 电压类型 val cellConnectorType: String = "", // 接口类型 val cellRepairType: String = "", // 格口维修类型 val devType: String = "", // 设备类型 val devCheckType: String = "", // 设备检测类型 val devSn: String = "", // 设备序列号 val devPvd: String = "", // 设备PID/VID val devPro: String = "", // 设备产品名 val devMfg: String = "", // 设备制造商 val devPwr: String = "", // 设备电量 val devState: String = "", // 设备状态 val devRepairType: String = "" // 设备维修类型 ) /** * 借还设备事件 */ data class DeviceEvent( val opType: String, // 操作类型 val deviceCode: String, // 设备编码(柜子SN) val cabinetName: String, // 柜子名称 val devSn: String, // 设备序列号 val doorName: String, // 格口名称 val devType: String, // 设备类型 val userId: String, // 用户ID val batchId: String, // 批次ID val opTime: String, // 操作时间 val doorInfo: DoorInfo // 格口信息 ) // ==================== MQTT 服务接口 ==================== interface IMqttService { val connectionState: StateFlow val isMqttConnect: Boolean // 初始化和连接 fun mqttInit(hostIp: String, deviceSn: String, devicePassword: String) fun mqttConnect() fun mqttClose() // 订阅 fun subscribeUpdate() fun subscribeLog() fun subscribeUploadLog() // 发布 - 设备状态同步 fun publishDb(doorInfo: DoorInfo) fun publishBorrowdevEvent(event: DeviceEvent) fun publishReturndevEvent(event: DeviceEvent) fun publishLosedevEvent(event: DeviceEvent) // 发布 - 系统消息 fun publishSoftwareVersion(version: String) fun publishDownLoadReply(code: String, messageId: String, taskId: String) fun publishLogupload(messageId: String, filename: String, fileUrl: String) fun publishInitDeviceReply(messageId: String, opType: String) fun publishCellSetReply(messageId: String) // 回调设置 fun setMessageCallback(callback: (topic: String, message: String) -> Unit) fun setConnectionCallback(callback: (MqttConnectionState) -> Unit) } // ==================== MQTT 服务实现(模拟)==================== class MqttServiceImpl private constructor( private val context: Context ) : IMqttService { companion object { @Volatile private var instance: MqttServiceImpl? = null fun getInstance(context: Context): MqttServiceImpl { return instance ?: synchronized(this) { instance ?: MqttServiceImpl(context.applicationContext).also { instance = it } } } } // 状态 private val _connectionState = MutableStateFlow(MqttConnectionState.DISCONNECTED) override val connectionState: StateFlow = _connectionState.asStateFlow() override val isMqttConnect: Boolean get() = _connectionState.value == MqttConnectionState.CONNECTED // 配置 private var hostIp: String = "" private var deviceSn: String = "" private var clientId: String = "" private var username: String = "" private var password: String = "" // 主题 private var topicPublishDb: String = "" private var topicPublishSoftwareVersion: String = "" private var topicPublishUpdateReply: String = "" private var topicPublishLogUpload: String = "" private var topicPublishInitDeviceReply: String = "" private var topicPublishBorrowDev: String = "" private var topicPublishReturnDev: String = "" private var topicPublishLoseDev: String = "" private var topicSubscribeUpdate: String = "" private var topicSubscribePropertySet: String = "" private var topicSubscribeServer: String = "" // 回调 private var messageCallback: ((String, String) -> Unit)? = null private var connectionCallback: ((MqttConnectionState) -> Unit)? = null // SharedPreferences private val sharedPreferences: SharedPreferences by lazy { context.getSharedPreferences("mqtt_prefs", Context.MODE_PRIVATE) } // TODO: 真实 MQTT 客户端 // private var mqttAndroidClient: MqttAndroidClient? = null // private var mqttConnectOptions: MqttConnectOptions? = null // ==================== 初始化 ==================== override fun mqttInit(hostIp: String, deviceSn: String, devicePassword: String) { if (hostIp.isEmpty()) { Log.w(MqttConstants.TAG, "mqttInit: hostIp is empty") return } Log.d(MqttConstants.TAG, "mqttInit") this.hostIp = hostIp this.deviceSn = deviceSn // 初始化主题(与原代码一致) topicPublishDb = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_EVENT}" topicPublishSoftwareVersion = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_PROP}" topicPublishUpdateReply = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_UPDATEREPLY}" topicPublishLogUpload = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_MESSAGEREPLY}" topicPublishInitDeviceReply = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_MESSAGEREPLY}" topicPublishBorrowDev = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_EVENT}" topicPublishReturnDev = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_EVENT}" topicPublishLoseDev = "${MqttConstants.PUBLISH_HEAD}$deviceSn${MqttConstants.PUBLISH_TAIL_EVENT}" topicSubscribeUpdate = "${MqttConstants.SUBSCRIBE_HEAD}$deviceSn${MqttConstants.SUBSCRIBE_TAIL_UPDATE}" topicSubscribePropertySet = "${MqttConstants.SUBSCRIBE_HEAD}$deviceSn${MqttConstants.SUBSCRIBE_TAIL_PROPERTYSET}" topicSubscribeServer = "${MqttConstants.SUBSCRIBE_HEAD}$deviceSn${MqttConstants.SUBSCRIBE_TAIL_SERVER}" // 生成认证信息(与原代码一致) clientId = deviceSn username = "$deviceSn|${System.currentTimeMillis()}" password = md5("$username|$devicePassword") Log.d(MqttConstants.TAG, "CLIENTID: $clientId") Log.d(MqttConstants.TAG, "USERNAME: $username") val serverUri = "tcp://$hostIp${MqttConstants.HOST_PORT}" Log.d(MqttConstants.TAG, "serverURI: $serverUri") // TODO: 初始化真实 MQTT 客户端 // mqttAndroidClient = MqttAndroidClient(context, serverUri, clientId) // mqttAndroidClient?.setCallback(mqttCallback) // mqttConnectOptions = MqttConnectOptions().apply { // isCleanSession = true // connectionTimeout = 10 // keepAliveInterval = 20 // userName = this@MqttServiceImpl.username // password = this@MqttServiceImpl.password.toCharArray() // } } override fun mqttConnect() { Log.d(MqttConstants.TAG, "mqttConnect") _connectionState.value = MqttConnectionState.CONNECTING connectionCallback?.invoke(MqttConnectionState.CONNECTING) // TODO: 真实连接 // try { // mqttAndroidClient?.connect(mqttConnectOptions, null, object : IMqttActionListener { // override fun onSuccess(asyncActionToken: IMqttToken?) { // Log.d(TAG, "MQTT连接成功!") // _connectionState.value = MqttConnectionState.CONNECTED // connectionCallback?.invoke(MqttConnectionState.CONNECTED) // publishSoftwareVersion(getSoftwareVersion()) // subscribeUpdate() // subscribeLog() // subscribeUploadLog() // } // override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) { // Log.d(TAG, "MQTT连接失败!") // mqttClose() // } // }) // } catch (e: MqttException) { // e.printStackTrace() // } // 模拟连接成功 _connectionState.value = MqttConnectionState.CONNECTED connectionCallback?.invoke(MqttConnectionState.CONNECTED) sharedPreferences.edit().putBoolean("connected", true).apply() } override fun mqttClose() { Log.d(MqttConstants.TAG, "mqttClose") _connectionState.value = MqttConnectionState.DISCONNECTED connectionCallback?.invoke(MqttConnectionState.DISCONNECTED) sharedPreferences.edit().putBoolean("connected", false).apply() // TODO: 断开真实连接 // try { // mqttAndroidClient?.disconnect() // } catch (e: MqttException) { // e.printStackTrace() // } } // ==================== 订阅 ==================== override fun subscribeUpdate() { if (!isMqttConnect) return Log.d(MqttConstants.TAG, "subscribeUpdate: $topicSubscribeUpdate") // TODO: mqttAndroidClient?.subscribe(topicSubscribeUpdate, MqttConstants.QOS_DEFAULT) } override fun subscribeLog() { if (!isMqttConnect) return Log.d(MqttConstants.TAG, "subscribeLog: $topicSubscribePropertySet") // TODO: mqttAndroidClient?.subscribe(topicSubscribePropertySet, MqttConstants.QOS_DEFAULT) } override fun subscribeUploadLog() { if (!isMqttConnect) return Log.d(MqttConstants.TAG, "subscribeUploadLog: $topicSubscribeServer") // TODO: mqttAndroidClient?.subscribe(topicSubscribeServer, MqttConstants.QOS_DEFAULT) } // ==================== 发布 - 设备状态 ==================== override fun publishDb(doorInfo: DoorInfo) { val message = buildDoorInfoSyncEvent(doorInfo) publish(topicPublishDb, message) } override fun publishBorrowdevEvent(event: DeviceEvent) { val message = buildBorrowDeviceEvent(event) publish(topicPublishBorrowDev, message) } override fun publishReturndevEvent(event: DeviceEvent) { val message = buildReturnDeviceEvent(event) publish(topicPublishReturnDev, message) } override fun publishLosedevEvent(event: DeviceEvent) { val message = buildLoseDeviceEvent(event) publish(topicPublishLoseDev, message) } // ==================== 发布 - 系统消息 ==================== override fun publishSoftwareVersion(version: String) { val message = """{"identifier":"firmware_version","firmware_version":"$version"}""" publish(topicPublishSoftwareVersion, message) } override fun publishDownLoadReply(code: String, messageId: String, taskId: String) { val replyMsg = when (code) { "0" -> "等待升级" "1" -> "已发送设备" "2" -> "设备收到" "3" -> "升级成功" "4" -> "升级失败" "5" -> "设备离线停止推送" else -> "未知错误码" } val message = """{"messageId":"$messageId","result":{"code":"$code","msg":"$replyMsg","taskId":"$taskId"}}""" publish(topicPublishUpdateReply, message) } override fun publishLogupload(messageId: String, filename: String, fileUrl: String) { val message = """{"messageId":"$messageId","result":{"code":"200","msg":{"type":"upload_log_reply","fileName":"$filename","fileUrl":"$fileUrl"}}}""" publish(topicPublishLogUpload, message) } override fun publishInitDeviceReply(messageId: String, opType: String) { val dstr = getDataString() val message = """{"messageId":"$messageId","result":{"code":"200","msg":{"type":"init_device_reply","status":"ok","optype":"$opType","time":"$dstr"}}}""" publish(topicPublishInitDeviceReply, message) } override fun publishCellSetReply(messageId: String) { val dstr = getDataString() val message = """{"messageId":"$messageId","result":{"code":"200","msg":{"type":"door_set_reply","status":"ok","time":"$dstr"}}}""" publish(topicPublishInitDeviceReply, message) } // ==================== 回调 ==================== override fun setMessageCallback(callback: (topic: String, message: String) -> Unit) { messageCallback = callback } override fun setConnectionCallback(callback: (MqttConnectionState) -> Unit) { connectionCallback = callback } // ==================== 内部方法 ==================== private fun publish(topic: String, message: String) { if (!isMqttConnect) { Log.w(MqttConstants.TAG, "publish failed: not connected") return } Log.d(MqttConstants.TAG, "publish to $topic: $message") // TODO: 真实发布 // try { // mqttAndroidClient?.publish(topic, message.toByteArray(), MqttConstants.QOS_DEFAULT, false) // } catch (e: MqttException) { // e.printStackTrace() // } } /** * 处理收到的消息(由 MqttCallback 调用) */ internal fun handleMessage(topic: String, payload: String) { Log.d(MqttConstants.TAG, "MQTT收到消息:topic:$topic msg:$payload") messageCallback?.invoke(topic, payload) try { when (topic) { topicSubscribeUpdate -> handleUpdateMessage(payload) topicSubscribePropertySet -> handlePropertySetMessage(payload) topicSubscribeServer -> handleServerMessage(payload) } } catch (e: Exception) { Log.e(MqttConstants.TAG, "handleMessage error", e) } } private fun handleUpdateMessage(payload: String) { val jsonObject = JSONObject(payload) val body = jsonObject.getString("body") val bodyJson = JSONObject(body) val messageId = bodyJson.getString("messageId") val taskId = bodyJson.getString("taskId") val firmwareVersion = bodyJson.getString("firmwareVersion") val url = bodyJson.getString("url") sharedPreferences.edit().apply { putString("fw", firmwareVersion) putString("url", url) putString("mid", messageId) putString("tid", taskId) putString("vcheck", "1") apply() } } private fun handlePropertySetMessage(payload: String) { val jsonObject = JSONObject(payload) val identifier = jsonObject.getString("identifier") val body = jsonObject.getString("body") Log.d(MqttConstants.TAG, "PropertySet: $identifier = $body") } private fun handleServerMessage(payload: String) { val jsonObject = JSONObject(payload) val identifier = jsonObject.getString("identifier") Log.d(MqttConstants.TAG, "Server identifier: $identifier") // 根据 identifier 处理不同的服务调用 } // ==================== 消息构建 ==================== private fun buildDoorInfoSyncEvent(info: DoorInfo): String { return """{"door_info_sync_event":{"devType":"${info.devType}","devSn":"${info.devSn}","devCheckType":"${info.devCheckType}","cabinetName":"${info.cabinetName}","doorState":"${info.cellState}","connectorType":"${info.cellConnectorType}","devPvd":"${info.devPvd}","doorName":"${info.cellName}","doorRepairType":"${info.cellRepairType}","devRepairType":"${info.devRepairType}","devPro":"${info.devPro}","devState":"${info.devState}","devMfg":"${info.devMfg}","devPwr":"${info.devPwr}","doorVoltageType":"${info.cellVoltageType}"}}""" } private fun buildBorrowDeviceEvent(event: DeviceEvent): String { val doorInfo = event.doorInfo return """{"borrow_device_event":{"opType":"${event.opType}","deviceCode":"${event.deviceCode}","cabinetName":"${event.cabinetName}","devSn":"${event.devSn}","doorName":"${event.doorName}","devType":"${event.devType}","userId":"${event.userId}","batchId":"${event.batchId}","borrowTime":"${event.opTime}","doorInfo":${buildDoorInfoJson(doorInfo)}}}""" } private fun buildReturnDeviceEvent(event: DeviceEvent): String { val doorInfo = event.doorInfo return """{"return_device_event":{"opType":"${event.opType}","deviceCode":"${event.deviceCode}","cabinetName":"${event.cabinetName}","devSn":"${event.devSn}","doorName":"${event.doorName}","devType":"${event.devType}","userId":"${event.userId}","batchId":"${event.batchId}","doorRepairType":"${doorInfo.cellRepairType}","devRepairType":"${doorInfo.devRepairType}","returnTime":"${event.opTime}","doorInfo":${buildDoorInfoJson(doorInfo)}}}""" } private fun buildLoseDeviceEvent(event: DeviceEvent): String { val doorInfo = event.doorInfo return """{"lose_device_event":{"opType":"${event.opType}","deviceCode":"${event.deviceCode}","cabinetName":"${event.cabinetName}","devSn":"${event.devSn}","doorName":"${event.doorName}","devType":"${event.devType}","userId":"${event.userId}","batchId":"${event.batchId}","doorRepairType":"${doorInfo.cellRepairType}","devRepairType":"${doorInfo.devRepairType}","loseTime":"${event.opTime}","doorInfo":${buildDoorInfoJson(doorInfo)}}}""" } private fun buildDoorInfoJson(info: DoorInfo): String { return """{"devType":"${info.devType}","devSn":"${info.devSn}","devCheckType":"${info.devCheckType}","cabinetName":"${info.cabinetName}","doorState":"${info.cellState}","connectorType":"${info.cellConnectorType}","devPvd":"${info.devPvd}","doorName":"${info.cellName}","doorRepairType":"${info.cellRepairType}","devRepairType":"${info.devRepairType}","devPro":"${info.devPro}","devState":"${info.devState}","devMfg":"${info.devMfg}","devPwr":"${info.devPwr}","doorVoltageType":"${info.cellVoltageType}"}""" } // ==================== 工具方法 ==================== private fun md5(input: String): String { return try { val md = MessageDigest.getInstance("MD5") val digest = md.digest(input.toByteArray()) digest.joinToString("") { "%02x".format(it) } } catch (e: NoSuchAlgorithmException) { "" } } private fun getDataString(): String { val sdf = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.getDefault()) return sdf.format(java.util.Date()) } } // ==================== 便捷扩展 ==================== fun Context.getMqttService(): IMqttService = MqttServiceImpl.getInstance(this)