Mercury: verify handshake echo, autolock endpoint, ringInfo-safe status
This commit is contained in:
@@ -61,7 +61,11 @@ object DebugHttpServer {
|
||||
val parts = requestLine.split(" ")
|
||||
if (parts.size < 2) return
|
||||
val method = parts[0]
|
||||
val path = parts[1]
|
||||
val rawPath = parts[1]
|
||||
val path = rawPath.substringBefore("?")
|
||||
val queryParams = rawPath.substringAfter("?", "").split("&")
|
||||
.filter { it.isNotEmpty() }
|
||||
.associate { it.substringBefore("=") to it.substringAfter("=") }
|
||||
|
||||
// Тело (до Content-Length)
|
||||
var contentLength = 0
|
||||
@@ -141,6 +145,12 @@ object DebugHttpServer {
|
||||
response = """{"ok":$ok,"status":${jsonEscape(MercuryBridge.state.value.statusText)}}"""
|
||||
code = 200
|
||||
}
|
||||
method == "POST" && path.startsWith("/mercury/autolock") -> {
|
||||
val time = queryParams["time"]?.toIntOrNull() ?: 10
|
||||
val ok = kotlinx.coroutines.runBlocking { MercuryBridge.autoLockTime(time) }
|
||||
response = """{"ok":$ok,"status":${jsonEscape(MercuryBridge.state.value.statusText)}}"""
|
||||
code = 200
|
||||
}
|
||||
else -> {
|
||||
response = """{"error":"нет такого пути: $method $path"}"""
|
||||
code = 404
|
||||
|
||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import pw.binom.mercury.DiscoveredGlasses
|
||||
import pw.binom.mercury.RayNeoMercury
|
||||
|
||||
@@ -43,16 +44,38 @@ object MercuryBridge {
|
||||
|
||||
private var currentGlasses: DiscoveredGlasses? = null
|
||||
|
||||
/** Инициализация (вызывать из Application). */
|
||||
/** Инициализация (вызывать из Application). Стартует GATT-сервер: очки сами нас найдут. */
|
||||
fun init(context: Context) {
|
||||
if (mercury != null) return
|
||||
mercury = RayNeoMercury(context.applicationContext, scope)
|
||||
scope.launch {
|
||||
mercury!!.events.collect { event ->
|
||||
when (event) {
|
||||
is RayNeoMercury.Event.Connected -> setStatus("подключено (BLE)")
|
||||
is RayNeoMercury.Event.Disconnected -> setStatus("отключено: ${event.cause}")
|
||||
is RayNeoMercury.Event.JsonMessage -> log("json", "${event.uri} ${event.json.take(200)}")
|
||||
is RayNeoMercury.Event.Connected -> {
|
||||
_state.value = _state.value.copy(connected = true)
|
||||
setStatus("подключено (BLE)")
|
||||
}
|
||||
is RayNeoMercury.Event.Disconnected -> {
|
||||
_state.value = _state.value.copy(connected = false)
|
||||
setStatus("отключено: ${event.cause}")
|
||||
}
|
||||
is RayNeoMercury.Event.JsonMessage -> {
|
||||
log("json", "${event.uri} ${event.json.take(200)}")
|
||||
// Очки шлют ble.request.* и ждут ble.response.* с тем же id.
|
||||
// Без ответа они рвут соединение — отвечаем эхом cmd/payload.
|
||||
val uri = event.uri
|
||||
val id = event.id
|
||||
if (uri != null && id != null && uri.startsWith("ble.request.") &&
|
||||
uri != "ble.request.general.status" && uri != "ble.request.general.settings"
|
||||
) {
|
||||
val respUri = uri.replace("ble.request.", "ble.response.")
|
||||
val payload = parsePayload(event.json)
|
||||
scope.launch {
|
||||
mercury?.send(pw.binom.mercury.model.PayloadMessage(respUri, id, payload))
|
||||
log("resp", "answered $uri -> $respUri")
|
||||
}
|
||||
}
|
||||
}
|
||||
is RayNeoMercury.Event.StreamPacket -> log("stream", "packet seq=${event.seq} type=${event.type} ${event.payload.size}B")
|
||||
is RayNeoMercury.Event.AppInstallStatus -> setStatus("app: ${event.pkg} status=${event.status} err=${event.errCode}")
|
||||
is RayNeoMercury.Event.AppInstallProgress -> log("app", "${event.pkg}: ${event.progress}%")
|
||||
@@ -63,6 +86,9 @@ object MercuryBridge {
|
||||
}
|
||||
}
|
||||
}
|
||||
val started = mercury!!.startServer()
|
||||
log("server", if (started) "GATT-сервер + реклама запущены" else "GATT-сервер НЕ стартовал")
|
||||
setStatus(if (started) "рекламирую сервис… жду очки" else "ошибка старта GATT-сервера")
|
||||
}
|
||||
|
||||
/** Прав на BLE нет — вернуть false; хост-приложение запрашивает само. */
|
||||
@@ -73,27 +99,39 @@ object MercuryBridge {
|
||||
ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
/** Скан + pair (если нужно) + connect. Возвращает true при успехе. */
|
||||
suspend fun connectFirst(timeoutMillis: Long = 20_000): Boolean {
|
||||
/**
|
||||
* Подключение: гарантирует, что GATT-сервер рекламируется, и ждёт, пока
|
||||
* очки сами подключатся к нам (они действуют как GATT-клиент). Если очки
|
||||
* всё же рекламируют — дополнительно пробуем скан-путь (клиент).
|
||||
*/
|
||||
suspend fun connectFirst(timeoutMillis: Long = 25_000): Boolean {
|
||||
val m = mercury ?: return false
|
||||
setStatus("сканирую очки…")
|
||||
val glasses = m.scanOnce(timeoutMillis)
|
||||
if (glasses == null) {
|
||||
setError("очки не найдены (скан $timeoutMillis мс)")
|
||||
if (!m.isServerRunning && !m.startServer()) {
|
||||
setError("не удалось запустить GATT-сервер")
|
||||
return false
|
||||
}
|
||||
currentGlasses = glasses
|
||||
setStatus("найдены: ${glasses.name ?: glasses.address}")
|
||||
|
||||
val pairResult = m.pair(glasses)
|
||||
if (pairResult is pw.binom.mercury.pair.PairManager.PairingResult.Failed) {
|
||||
log("pair", "failed: ${pairResult.reason}")
|
||||
setStatus("рекламирую сервис… жду очки")
|
||||
val deadline = System.currentTimeMillis() + timeoutMillis
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
if (m.isAnyConnected) {
|
||||
_state.value = _state.value.copy(connected = true)
|
||||
setStatus("подключено (BLE)")
|
||||
return true
|
||||
}
|
||||
|
||||
val ok = m.connect(glasses)
|
||||
if (!ok) setError("connect не удался")
|
||||
_state.value = _state.value.copy(connected = ok, glassesName = glasses.name)
|
||||
return ok
|
||||
// На случай, если очки рекламируются сами — клиентский путь.
|
||||
val glasses = m.scanOnce(3000)
|
||||
if (glasses != null) {
|
||||
setStatus("найдены: ${glasses.name ?: glasses.address}, подключаюсь…")
|
||||
currentGlasses = glasses
|
||||
if (m.connect(glasses, 8000)) {
|
||||
_state.value = _state.value.copy(connected = true, glassesName = glasses.name)
|
||||
setStatus("подключено (клиент)")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
setError("очки не подключились за ${timeoutMillis / 1000} с")
|
||||
return false
|
||||
}
|
||||
|
||||
suspend fun disconnect() {
|
||||
@@ -116,6 +154,13 @@ object MercuryBridge {
|
||||
return ok
|
||||
}
|
||||
|
||||
/** Автоблокировка экрана через N секунд; -1 = никогда. */
|
||||
suspend fun autoLockTime(time: Int): Boolean {
|
||||
val ok = mercury?.setAutoLockTime(time) ?: false
|
||||
setStatus(if (ok) "auto_lock_time=$time отправлен" else "ошибка отправки (нет связи?)")
|
||||
return ok
|
||||
}
|
||||
|
||||
private fun setStatus(text: String) {
|
||||
log("status", text)
|
||||
_state.value = _state.value.copy(statusText = text, lastError = null)
|
||||
@@ -126,6 +171,14 @@ object MercuryBridge {
|
||||
_state.value = _state.value.copy(statusText = text, lastError = text)
|
||||
}
|
||||
|
||||
private fun parsePayload(json: String): pw.binom.mercury.model.Payload? = try {
|
||||
val obj = kotlinx.serialization.json.Json.parseToJsonElement(json).jsonObject
|
||||
val el = obj["payload"] ?: return null
|
||||
kotlinx.serialization.json.Json.decodeFromJsonElement(pw.binom.mercury.model.Payload.serializer(), el)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun log(tag: String, msg: String) {
|
||||
Log.d(TAG, "[$tag] $msg")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user