android: remove demo synthetic thermal image (real-device display test); rebuild toolchain+APK on this machine
This commit is contained in:
@@ -2,13 +2,10 @@ package com.mag160c.thermal.ui.live
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mag160c.thermal.core.TempMath
|
||||
import com.mag160c.thermal.usb.IrSession
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Live view state machine: USB permission -> link -> stream -> OSD stats.
|
||||
@@ -96,13 +93,17 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Begin USB permission flow, then start streaming. No device -> demo mode. */
|
||||
/** Begin USB permission flow, then start streaming. No device -> idle notice. */
|
||||
fun connect() {
|
||||
val context = getApplication<Application>()
|
||||
val transport = com.mag160c.thermal.usb.UsbTransport(context)
|
||||
val dev = transport.findDevice()
|
||||
if (dev == null) {
|
||||
startDemo()
|
||||
_state.value = _state.value.copy(
|
||||
connected = false,
|
||||
streaming = false,
|
||||
status = "no_device",
|
||||
)
|
||||
return
|
||||
}
|
||||
transport.requestPermission { ok ->
|
||||
@@ -116,87 +117,6 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
|
||||
}
|
||||
}
|
||||
|
||||
private var demoPipeline: com.mag160c.thermal.core.RenderPipeline? = null
|
||||
private var demoJob: kotlinx.coroutines.Job? = null
|
||||
|
||||
/**
|
||||
* Demo mode: feed synthetic frames through the REAL render pipeline
|
||||
* (official DDT bundled) so the UI is fully explorable without hardware.
|
||||
*/
|
||||
private fun startDemo() {
|
||||
if (demoJob?.isActive == true) return
|
||||
_state.value = _state.value.copy(connected = true, streaming = true, status = "demo")
|
||||
val frames = buildDemoFrames()
|
||||
demoJob = viewModelScope.launch(kotlinx.coroutines.Dispatchers.Default) {
|
||||
val ddt = runCatching {
|
||||
getApplication<Application>().assets.open("mag160c.ddt").readBytes()
|
||||
}.getOrDefault(ByteArray(0))
|
||||
val pipe = com.mag160c.thermal.core.RenderPipeline(160, 120, onFfc = {})
|
||||
if (!pipe.loadDdt(ddt)) return@launch
|
||||
demoPipeline = pipe
|
||||
val out = IntArray(320 * 240)
|
||||
var count = 0
|
||||
while (demoPipeline != null) {
|
||||
for (f in frames.indices) {
|
||||
val p = demoPipeline ?: return@launch
|
||||
if (p.frame(frames[f], true, out)) {
|
||||
latestFrame = out.copyOf()
|
||||
count++
|
||||
if (count % 8 == 0) refreshTemps()
|
||||
}
|
||||
delay(66)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthetic 60-frame cycle (gradient scene + slow shutter drift). */
|
||||
private fun buildDemoFrames(): ArrayList<ByteArray> {
|
||||
val frameSize = 0x38 + 38400
|
||||
val frames = ArrayList<ByteArray>(60)
|
||||
var seed = 20260906
|
||||
for (f in 0 until 60) {
|
||||
val buf = ByteArray(frameSize)
|
||||
var acc = seed
|
||||
fun next(): Int {
|
||||
acc = (acc * 1103515245 + 12345) and 0x7FFFFFFF
|
||||
return acc % 97 - 48
|
||||
}
|
||||
seed = acc
|
||||
for (i in 0 until 19200) {
|
||||
val x = i % 160
|
||||
val y = i / 160
|
||||
var base = x * 4 + y * 3 + 14000
|
||||
// warm block (like a hand in frame), upper-right area
|
||||
if (x in 96..144 && y in 28..76) base += 620
|
||||
if (x in 104..136 && y in 36..68) base += 180
|
||||
val v = (base + next()).coerceIn(0, 65535)
|
||||
val off = 0x1C + i * 2
|
||||
buf[off] = (v and 0xFF).toByte()
|
||||
buf[off + 1] = ((v shr 8) and 0xFF).toByte()
|
||||
}
|
||||
frames.add(buf)
|
||||
}
|
||||
// headers + trailers
|
||||
for (f in frames.indices) {
|
||||
val buf = frames[f]
|
||||
put32(buf, 0, 0x1BB1B11B)
|
||||
put32(buf, 4, f)
|
||||
put32(buf, 8, 38400)
|
||||
put32(buf, 12, if (f in 6..9) 1 else 0)
|
||||
put32(buf, 16, 4096 + f / 4)
|
||||
put32(buf, 0x1C + 38400, 0x1BB1B11C)
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
private fun put32(dst: ByteArray, off: Int, v: Int) {
|
||||
dst[off] = (v and 0xFF).toByte()
|
||||
dst[off + 1] = ((v shr 8) and 0xFF).toByte()
|
||||
dst[off + 2] = ((v shr 16) and 0xFF).toByte()
|
||||
dst[off + 3] = ((v ushr 24) and 0xFF).toByte()
|
||||
}
|
||||
|
||||
fun disconnect() = session.stop()
|
||||
|
||||
fun triggerFfc() = session.triggerFfc()
|
||||
@@ -333,37 +253,6 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
|
||||
|
||||
/** Update per-frame temperature stats (called on a slow timer). */
|
||||
fun refreshTemps() {
|
||||
val demo = demoPipeline
|
||||
if (demo != null) {
|
||||
// Demo-only display mapping (true calibration needs hardware).
|
||||
demo.copyNuc(demoNuc)
|
||||
var mean = 0L
|
||||
var mn = Int.MAX_VALUE
|
||||
var mx = -1
|
||||
var mnPos = -1
|
||||
var mxPos = -1
|
||||
for (i in demoNuc.indices) {
|
||||
val v = demoNuc[i]
|
||||
mean += v
|
||||
if (v < mn) { mn = v; mnPos = i }
|
||||
if (v > mx) { mx = v; mxPos = i }
|
||||
}
|
||||
mean /= demoNuc.size
|
||||
val mv = mean.toFloat()
|
||||
val probes = _state.value.probes.map { p ->
|
||||
val v = demoNuc[p.y * 160 + p.x]
|
||||
p.copy(tempC = 24f + (v - mean) * 0.02f)
|
||||
}
|
||||
_state.value = _state.value.copy(
|
||||
centerTempC = 24f + (demoNuc[60 * 160 + 80] - mean) * 0.02f,
|
||||
maxTempC = 24f + (mx - mean) * 0.02f,
|
||||
minTempC = 24f + (mn - mean) * 0.02f,
|
||||
maxPos = mxPos,
|
||||
minPos = mnPos,
|
||||
probes = probes,
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!session.isStreaming()) return
|
||||
val center = session.probeTemp(80, 60)
|
||||
val nuc = IntArray(19200)
|
||||
@@ -371,8 +260,6 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
|
||||
updateTemps(center, nuc)
|
||||
}
|
||||
|
||||
private val demoNuc = IntArray(19200)
|
||||
|
||||
private fun updateTemps(center: Int?, nuc: IntArray) {
|
||||
var mn = Int.MAX_VALUE
|
||||
var mx = -1
|
||||
@@ -403,7 +290,6 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
demoPipeline = null
|
||||
session.destroy()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user