apps\iot\src\main\java\cn\nfarms\iot\IotViewModel.kt¶
源码路径:
apps\iot\src\main\java\cn\nfarms\iot\IotViewModel.kt· 120 行
package cn.nfarms.iot
import android.app.Application
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import cn.nfarms.iot.device.DeviceSession
import cn.nfarms.iot.device.DeviceUi
import cn.nfarms.iot.device.LinkState
import cn.nfarms.iot.ota.FirmwareFactory
import cn.nfarms.iot.ota.OtaPackage
import cn.nfarms.iot.ota.OtaVerifier
import cn.nfarms.iot.transport.MockTransport
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class IotApp : Application() {
lateinit var container: Container
private set
override fun onCreate() {
super.onCreate()
container = Container(this)
}
}
class Container(private val context: Context) {
/** 演示固件签名的密钥对(生产必须换成厂商公钥,见 OtaVerifier 注释)。 */
val fw by lazy { FirmwareFactory() }
fun newSession(scope: kotlinx.coroutines.CoroutineScope): Pair<DeviceSession, MockTransport> {
val transport = MockTransport()
// 设备当前版本是 1.4.2;演示包做成 1.5.0,升级后版本号应当变化
transport.pendingVersion = "1.5.0"
val verifier = OtaVerifier(fw.publicKeyDer())
val session = DeviceSession(scope, transport, verifier, DEVICE_TARGET)
return session to transport
}
fun context(): Context = context
companion object {
const val DEVICE_TARGET = "NF-Demo-01"
}
}
/**
* IOT 界面状态。
*
* 只包一层 [DeviceUi],外加当前演示升级包 —— 拒绝路径是这个 App 最值得看的部分,
* 所以"包本身"要能显示出来(版本、机型、SHA-256)。
*/
data class IotUi(
val dev: DeviceUi = DeviceUi(),
val pkg: OtaPackage? = null,
)
class IotViewModel(private val c: Container) : ViewModel() {
private val pair = c.newSession(viewModelScope)
val session: DeviceSession = pair.first
private val transport: MockTransport = pair.second
private val _ui = MutableStateFlow(IotUi(pkg = c.fw.make("1.5.0", notes = "演示固件 1.5.0")))
val ui: StateFlow<IotUi> = _ui.asStateFlow()
init {
viewModelScope.launch {
session.ui.collect { dev -> _ui.value = _ui.value.copy(dev = dev) }
}
}
fun scan() = session.scan()
fun connect(id: String) = session.connect(id)
fun disconnect() = session.disconnect()
fun readStatus() = session.readStatus()
fun setPower(on: Boolean) = session.setPower(on)
fun setMode(m: Int) = session.setMode(m)
fun startOta() {
val p = _ui.value.pkg ?: return
session.runOta(p)
}
/** 把一个**坏包**塞进升级流程,用来验证拒绝路径。 */
fun tryBadPackage(kind: String) {
val base = c.fw.make("1.6.0", notes = "坏包演示")
val bad = when (kind) {
"content" -> c.fw.tamperedContent(base)
"key" -> c.fw.signedByOtherKey(base)
"downgrade" -> c.fw.downgrade(base)
else -> base
}
_ui.value = _ui.value.copy(pkg = bad)
session.runOta(bad)
}
fun resetGoodPackage() {
_ui.value = _ui.value.copy(pkg = c.fw.make("1.5.0", notes = "演示固件 1.5.0"))
}
fun dismissMessage() = session.dismissMessage()
fun isReady(): Boolean = _ui.value.dev.link == LinkState.Ready
val link: LinkState get() = _ui.value.dev.link
override fun onCleared() {
session.close()
super.onCleared()
}
}