v7.2: simplify - single Bee IP, fix device_id
This commit is contained in:
parent
8a63b1408d
commit
ef30ea4bb6
8 changed files with 58 additions and 171 deletions
|
|
@ -20,8 +20,7 @@ import okhttp3.Request
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class BeeApiClient(
|
class BeeApiClient(
|
||||||
private var primaryUrl: String = "http://192.168.0.10:5000",
|
private var apiUrl: String = "http://192.168.0.10:5000"
|
||||||
private var altUrl: String = "http://192.168.197.1:5000"
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "VarroaBeeAPI"
|
private const val TAG = "VarroaBeeAPI"
|
||||||
|
|
@ -31,22 +30,14 @@ class BeeApiClient(
|
||||||
private var fastClient = buildFastClient(null)
|
private var fastClient = buildFastClient(null)
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
|
|
||||||
// Track which URL is currently active (null = unknown/offline)
|
|
||||||
private var activeUrl: String? = null
|
|
||||||
|
|
||||||
// Connection state
|
// Connection state
|
||||||
var isConnected: Boolean = false
|
var isConnected: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun updateUrls(primary: String, alt: String) {
|
fun updateUrl(url: String) {
|
||||||
val oldPrimary = primaryUrl
|
val oldUrl = apiUrl
|
||||||
val oldAlt = altUrl
|
apiUrl = url.trimEnd('/')
|
||||||
primaryUrl = primary.trimEnd('/')
|
Log.d(TAG, "URL updated from $oldUrl to $apiUrl")
|
||||||
altUrl = alt.trimEnd('/')
|
|
||||||
Log.d(TAG, "URLs updated from [$oldPrimary, $oldAlt] to [$primaryUrl, $altUrl]")
|
|
||||||
// Reset active URL when settings change
|
|
||||||
activeUrl = null
|
|
||||||
Log.d(TAG, "Active URL reset due to configuration change")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,9 +94,8 @@ class BeeApiClient(
|
||||||
} catch (e: Exception) { null }
|
} catch (e: Exception) { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getRaw(path: String, useActiveUrl: Boolean = true): ApiResult<String> = withContext(Dispatchers.IO) {
|
private suspend fun getRaw(path: String): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||||
val baseUrl = if (useActiveUrl && activeUrl != null) activeUrl!! else primaryUrl
|
val fullUrl = "$apiUrl$path"
|
||||||
val fullUrl = "$baseUrl$path"
|
|
||||||
Log.d(TAG, "HTTP GET request to: $fullUrl")
|
Log.d(TAG, "HTTP GET request to: $fullUrl")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -114,7 +104,6 @@ class BeeApiClient(
|
||||||
val body = resp.body?.string() ?: ""
|
val body = resp.body?.string() ?: ""
|
||||||
if (resp.isSuccessful) {
|
if (resp.isSuccessful) {
|
||||||
isConnected = true
|
isConnected = true
|
||||||
activeUrl = baseUrl
|
|
||||||
Log.d(TAG, "HTTP ${resp.code} OK - response length: ${body.length} chars")
|
Log.d(TAG, "HTTP ${resp.code} OK - response length: ${body.length} chars")
|
||||||
ApiResult.Success(body)
|
ApiResult.Success(body)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -129,9 +118,8 @@ class BeeApiClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getBytes(path: String): ApiResult<ByteArray> = withContext(Dispatchers.IO) {
|
private suspend fun getBytes(path: String): ApiResult<ByteArray> = withContext(Dispatchers.IO) {
|
||||||
val baseUrl = activeUrl ?: primaryUrl
|
|
||||||
try {
|
try {
|
||||||
val req = Request.Builder().url("$baseUrl$path").get().build()
|
val req = Request.Builder().url("$apiUrl$path").get().build()
|
||||||
client.newCall(req).execute().use { resp ->
|
client.newCall(req).execute().use { resp ->
|
||||||
val bytes = resp.body?.bytes() ?: ByteArray(0)
|
val bytes = resp.body?.bytes() ?: ByteArray(0)
|
||||||
if (resp.isSuccessful && bytes.isNotEmpty()) {
|
if (resp.isSuccessful && bytes.isNotEmpty()) {
|
||||||
|
|
@ -147,109 +135,38 @@ class BeeApiClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to ping the Bee at the given URL with a short timeout.
|
* Check if Bee is reachable.
|
||||||
* Returns the URL if successful, null otherwise.
|
|
||||||
*/
|
|
||||||
private suspend fun tryPing(url: String): String? = withContext(Dispatchers.IO) {
|
|
||||||
Log.d(TAG, "Ping attempt to: $url/api/1/info")
|
|
||||||
try {
|
|
||||||
val req = Request.Builder().url("$url/api/1/info").get().build()
|
|
||||||
fastClient.newCall(req).execute().use { resp ->
|
|
||||||
if (resp.isSuccessful) {
|
|
||||||
Log.d(TAG, "Ping successful: $url (HTTP ${resp.code})")
|
|
||||||
url
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "Ping failed: $url (HTTP ${resp.code})")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d(TAG, "Ping exception: $url - ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try both IPs in parallel with short timeouts. Return the first one that responds.
|
|
||||||
* If we already have an active URL, try it first (fast path).
|
|
||||||
*/
|
|
||||||
suspend fun discoverActiveUrl(): String? = withContext(Dispatchers.IO) {
|
|
||||||
// Fast path: if we have an active URL, try it first
|
|
||||||
if (activeUrl != null) {
|
|
||||||
Log.d(TAG, "Fast path - testing existing active URL: $activeUrl")
|
|
||||||
val result = tryPing(activeUrl!!)
|
|
||||||
if (result != null) {
|
|
||||||
isConnected = true
|
|
||||||
Log.i(TAG, "Active URL still works: $activeUrl")
|
|
||||||
return@withContext result
|
|
||||||
}
|
|
||||||
// Active URL failed, clear it and try both
|
|
||||||
Log.w(TAG, "Active URL $activeUrl failed - falling back to discovery")
|
|
||||||
activeUrl = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try both URLs in parallel
|
|
||||||
Log.d(TAG, "Starting parallel discovery: [$primaryUrl, $altUrl]")
|
|
||||||
val primaryDeferred = async { tryPing(primaryUrl) }
|
|
||||||
val altDeferred = async { tryPing(altUrl) }
|
|
||||||
|
|
||||||
// Wait for first success with timeout
|
|
||||||
val result = withTimeoutOrNull(4000L) {
|
|
||||||
val primary = primaryDeferred.await()
|
|
||||||
if (primary != null) {
|
|
||||||
Log.d(TAG, "Primary URL responded first, cancelling alt")
|
|
||||||
altDeferred.cancel()
|
|
||||||
return@withTimeoutOrNull primary
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Primary URL failed, waiting for alt")
|
|
||||||
altDeferred.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
activeUrl = result
|
|
||||||
isConnected = true
|
|
||||||
Log.i(TAG, "Discovery successful - active URL: $result")
|
|
||||||
} else {
|
|
||||||
isConnected = false
|
|
||||||
Log.w(TAG, "Discovery failed - no URLs responded within 4s timeout")
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if Bee is reachable (with discovery fallback).
|
|
||||||
* Updates internal connection state.
|
* Updates internal connection state.
|
||||||
*/
|
*/
|
||||||
suspend fun ping(): Boolean {
|
suspend fun ping(): Boolean {
|
||||||
val url = discoverActiveUrl()
|
return when (getRaw("/api/1/info")) {
|
||||||
return url != null
|
is ApiResult.Success -> {
|
||||||
|
isConnected = true
|
||||||
|
Log.i(TAG, "Ping successful")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
isConnected = false
|
||||||
|
Log.w(TAG, "Ping failed")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the currently active URL (or null if offline).
|
* Get the currently configured URL.
|
||||||
*/
|
*/
|
||||||
fun getActiveUrl(): String? = activeUrl
|
fun getApiUrl(): String = apiUrl
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force offline state (for exponential backoff scenarios).
|
* Force offline state (for exponential backoff scenarios).
|
||||||
*/
|
*/
|
||||||
fun setOffline() {
|
fun setOffline() {
|
||||||
isConnected = false
|
isConnected = false
|
||||||
activeUrl = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getLandmarks(): ApiResult<List<BeeDetection>> = withContext(Dispatchers.IO) {
|
suspend fun getLandmarks(): ApiResult<List<BeeDetection>> = withContext(Dispatchers.IO) {
|
||||||
Log.d(TAG, "getLandmarks() called - activeUrl: $activeUrl")
|
Log.d(TAG, "getLandmarks() called")
|
||||||
|
|
||||||
// If no active URL, try discovery first
|
|
||||||
if (activeUrl == null) {
|
|
||||||
Log.d(TAG, "No active URL - starting discovery...")
|
|
||||||
if (discoverActiveUrl() == null) {
|
|
||||||
isConnected = false
|
|
||||||
Log.e(TAG, "Discovery failed - marking Bee as offline")
|
|
||||||
return@withContext ApiResult.Error("Bee offline")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val r = getRaw("/api/1/landmarks/last/200")) {
|
when (val r = getRaw("/api/1/landmarks/last/200")) {
|
||||||
is ApiResult.Success -> try {
|
is ApiResult.Success -> try {
|
||||||
|
|
@ -265,13 +182,12 @@ class BeeApiClient(
|
||||||
}
|
}
|
||||||
is ApiResult.Error -> {
|
is ApiResult.Error -> {
|
||||||
Log.e(TAG, "getLandmarks() failed: ${r.message} (code: ${r.code})")
|
Log.e(TAG, "getLandmarks() failed: ${r.message} (code: ${r.code})")
|
||||||
// Connection failed, clear active URL for next attempt
|
// Connection failed, mark as offline
|
||||||
if (r.message.contains("timeout", ignoreCase = true) ||
|
if (r.message.contains("timeout", ignoreCase = true) ||
|
||||||
r.message.contains("connect", ignoreCase = true) ||
|
r.message.contains("connect", ignoreCase = true) ||
|
||||||
r.message.contains("refused", ignoreCase = true) ||
|
r.message.contains("refused", ignoreCase = true) ||
|
||||||
r.message.contains("unreachable", ignoreCase = true)) {
|
r.message.contains("unreachable", ignoreCase = true)) {
|
||||||
Log.d(TAG, "Network error detected - clearing active URL")
|
Log.d(TAG, "Network error detected - marking as offline")
|
||||||
activeUrl = null
|
|
||||||
isConnected = false
|
isConnected = false
|
||||||
}
|
}
|
||||||
r
|
r
|
||||||
|
|
@ -280,10 +196,6 @@ class BeeApiClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getGnss(): ApiResult<GnssData> = withContext(Dispatchers.IO) {
|
suspend fun getGnss(): ApiResult<GnssData> = withContext(Dispatchers.IO) {
|
||||||
if (activeUrl == null && discoverActiveUrl() == null) {
|
|
||||||
return@withContext ApiResult.Error("Bee offline")
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val r = getRaw("/api/1/gnssConcise/latestValid")) {
|
when (val r = getRaw("/api/1/gnssConcise/latestValid")) {
|
||||||
is ApiResult.Success -> try {
|
is ApiResult.Success -> try {
|
||||||
ApiResult.Success(gson.fromJson(r.data, GnssData::class.java))
|
ApiResult.Success(gson.fromJson(r.data, GnssData::class.java))
|
||||||
|
|
@ -295,10 +207,6 @@ class BeeApiClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getDeviceInfo(): ApiResult<BeeDeviceInfo> = withContext(Dispatchers.IO) {
|
suspend fun getDeviceInfo(): ApiResult<BeeDeviceInfo> = withContext(Dispatchers.IO) {
|
||||||
if (activeUrl == null && discoverActiveUrl() == null) {
|
|
||||||
return@withContext ApiResult.Error("Bee offline")
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val r = getRaw("/api/1/info")) {
|
when (val r = getRaw("/api/1/info")) {
|
||||||
is ApiResult.Success -> try {
|
is ApiResult.Success -> try {
|
||||||
ApiResult.Success(gson.fromJson(r.data, BeeDeviceInfo::class.java))
|
ApiResult.Success(gson.fromJson(r.data, BeeDeviceInfo::class.java))
|
||||||
|
|
@ -314,9 +222,6 @@ class BeeApiClient(
|
||||||
* The caller is responsible for trying fallback endpoints.
|
* The caller is responsible for trying fallback endpoints.
|
||||||
*/
|
*/
|
||||||
suspend fun getCameraFrame(endpoint: String): ApiResult<ByteArray> {
|
suspend fun getCameraFrame(endpoint: String): ApiResult<ByteArray> {
|
||||||
if (activeUrl == null && discoverActiveUrl() == null) {
|
|
||||||
return ApiResult.Error("Bee offline")
|
|
||||||
}
|
|
||||||
return getBytes(endpoint)
|
return getBytes(endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,10 +229,6 @@ class BeeApiClient(
|
||||||
* Try multiple camera endpoints in order, return first success.
|
* Try multiple camera endpoints in order, return first success.
|
||||||
*/
|
*/
|
||||||
suspend fun getCameraFrameAuto(configured: String): Pair<String, ApiResult<ByteArray>> {
|
suspend fun getCameraFrameAuto(configured: String): Pair<String, ApiResult<ByteArray>> {
|
||||||
if (activeUrl == null && discoverActiveUrl() == null) {
|
|
||||||
return configured to ApiResult.Error("Bee offline")
|
|
||||||
}
|
|
||||||
|
|
||||||
val candidates = listOf(
|
val candidates = listOf(
|
||||||
configured,
|
configured,
|
||||||
"/api/1/camera/frame",
|
"/api/1/camera/frame",
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ data class BeeDetection(
|
||||||
@SerializedName("width") val width: Double? = null,
|
@SerializedName("width") val width: Double? = null,
|
||||||
@SerializedName("height") val height: Double? = null,
|
@SerializedName("height") val height: Double? = null,
|
||||||
@SerializedName("pos_confidence") val posConfidence: Double? = null,
|
@SerializedName("pos_confidence") val posConfidence: Double? = null,
|
||||||
@SerializedName("azimuth") val azimuth: Double? = null
|
@SerializedName("azimuth") val azimuth: Double? = null,
|
||||||
|
@SerializedName("device_id") val deviceId: String? = null
|
||||||
) {
|
) {
|
||||||
/** Dedup key */
|
/** Dedup key */
|
||||||
fun dedupKey(): String = "${id}_${ts}"
|
fun dedupKey(): String = "${id}_${ts}"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(na
|
||||||
|
|
||||||
data class VarroaSettings(
|
data class VarroaSettings(
|
||||||
val beeApiUrl: String = "http://192.168.0.10:5000",
|
val beeApiUrl: String = "http://192.168.0.10:5000",
|
||||||
val beeAltApiUrl: String = "http://192.168.197.1:5000",
|
|
||||||
val adamapsApiUrl: String = "https://api.adamaps.org",
|
val adamapsApiUrl: String = "https://api.adamaps.org",
|
||||||
val adamapsApiKey: String = "***REMOVED***",
|
val adamapsApiKey: String = "***REMOVED***",
|
||||||
val pollIntervalSeconds: Int = 30,
|
val pollIntervalSeconds: Int = 30,
|
||||||
|
|
@ -28,7 +27,6 @@ class SettingsDataStore(private val context: Context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val KEY_BEE_URL = stringPreferencesKey("bee_api_url")
|
private val KEY_BEE_URL = stringPreferencesKey("bee_api_url")
|
||||||
private val KEY_BEE_ALT_URL = stringPreferencesKey("bee_alt_api_url")
|
|
||||||
private val KEY_ADAMAPS_URL = stringPreferencesKey("adamaps_api_url")
|
private val KEY_ADAMAPS_URL = stringPreferencesKey("adamaps_api_url")
|
||||||
private val KEY_ADAMAPS_KEY = stringPreferencesKey("adamaps_api_key")
|
private val KEY_ADAMAPS_KEY = stringPreferencesKey("adamaps_api_key")
|
||||||
private val KEY_POLL_INTERVAL = intPreferencesKey("poll_interval_seconds")
|
private val KEY_POLL_INTERVAL = intPreferencesKey("poll_interval_seconds")
|
||||||
|
|
@ -40,7 +38,6 @@ class SettingsDataStore(private val context: Context) {
|
||||||
val settings: Flow<VarroaSettings> = context.dataStore.data.map { prefs ->
|
val settings: Flow<VarroaSettings> = context.dataStore.data.map { prefs ->
|
||||||
VarroaSettings(
|
VarroaSettings(
|
||||||
beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5000",
|
beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5000",
|
||||||
beeAltApiUrl = prefs[KEY_BEE_ALT_URL] ?: "http://192.168.197.1:5000",
|
|
||||||
adamapsApiUrl = prefs[KEY_ADAMAPS_URL] ?: "https://api.adamaps.org",
|
adamapsApiUrl = prefs[KEY_ADAMAPS_URL] ?: "https://api.adamaps.org",
|
||||||
adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "***REMOVED***",
|
adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "***REMOVED***",
|
||||||
pollIntervalSeconds = prefs[KEY_POLL_INTERVAL] ?: 30,
|
pollIntervalSeconds = prefs[KEY_POLL_INTERVAL] ?: 30,
|
||||||
|
|
@ -53,7 +50,6 @@ class SettingsDataStore(private val context: Context) {
|
||||||
suspend fun save(s: VarroaSettings) {
|
suspend fun save(s: VarroaSettings) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[KEY_BEE_URL] = s.beeApiUrl
|
prefs[KEY_BEE_URL] = s.beeApiUrl
|
||||||
prefs[KEY_BEE_ALT_URL] = s.beeAltApiUrl
|
|
||||||
prefs[KEY_ADAMAPS_URL] = s.adamapsApiUrl
|
prefs[KEY_ADAMAPS_URL] = s.adamapsApiUrl
|
||||||
prefs[KEY_ADAMAPS_KEY] = s.adamapsApiKey
|
prefs[KEY_ADAMAPS_KEY] = s.adamapsApiKey
|
||||||
prefs[KEY_POLL_INTERVAL] = s.pollIntervalSeconds
|
prefs[KEY_POLL_INTERVAL] = s.pollIntervalSeconds
|
||||||
|
|
|
||||||
|
|
@ -138,10 +138,10 @@ class BeeCollectorService : LifecycleService() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
Log.d(TAG, "Loading settings from DataStore...")
|
Log.d(TAG, "Loading settings from DataStore...")
|
||||||
val settings = settingsStore.settings.first()
|
val settings = settingsStore.settings.first()
|
||||||
Log.d(TAG, "Settings loaded - beeApiUrl: ${settings.beeApiUrl}, altUrl: ${settings.beeAltApiUrl}, pollInterval: ${settings.pollIntervalSeconds}s")
|
Log.d(TAG, "Settings loaded - beeApiUrl: ${settings.beeApiUrl}, pollInterval: ${settings.pollIntervalSeconds}s")
|
||||||
|
|
||||||
beeClient.updateUrls(settings.beeApiUrl, settings.beeAltApiUrl)
|
beeClient.updateUrl(settings.beeApiUrl)
|
||||||
Log.d(TAG, "BeeApiClient URLs updated")
|
Log.d(TAG, "BeeApiClient URL updated")
|
||||||
|
|
||||||
// Bind to Bee network (unvalidated WiFi)
|
// Bind to Bee network (unvalidated WiFi)
|
||||||
Log.d(TAG, "Attempting to bind to Bee network...")
|
Log.d(TAG, "Attempting to bind to Bee network...")
|
||||||
|
|
@ -234,13 +234,9 @@ class BeeCollectorService : LifecycleService() {
|
||||||
_lastError.value = null
|
_lastError.value = null
|
||||||
Log.i(TAG, "Successfully connected to Bee - received ${result.data.size} detections")
|
Log.i(TAG, "Successfully connected to Bee - received ${result.data.size} detections")
|
||||||
|
|
||||||
val activeUrl = beeClient.getActiveUrl()
|
val apiUrl = beeClient.getApiUrl()
|
||||||
_beeStatus.value = if (activeUrl?.contains("155") == true) {
|
_beeStatus.value = "Connected"
|
||||||
"Connected (home WiFi)"
|
Log.d(TAG, "API URL: $apiUrl, Status: ${_beeStatus.value}")
|
||||||
} else {
|
|
||||||
"Connected (AP mode)"
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Active URL: $activeUrl, Status: ${_beeStatus.value}")
|
|
||||||
|
|
||||||
// Filter for new detections
|
// Filter for new detections
|
||||||
val newDetections = result.data.filter { d ->
|
val newDetections = result.data.filter { d ->
|
||||||
|
|
@ -287,7 +283,24 @@ class BeeCollectorService : LifecycleService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun storeDetections(detections: List<BeeDetection>) {
|
private suspend fun storeDetections(detections: List<BeeDetection>) {
|
||||||
val deviceId = _currentDeviceId.value
|
// Extract device_id from first detection if available, otherwise use cached value
|
||||||
|
val deviceIdFromDetection = detections.firstOrNull()?.deviceId
|
||||||
|
val deviceId = if (!deviceIdFromDetection.isNullOrBlank()) {
|
||||||
|
// Update cached device ID if found in detection
|
||||||
|
if (deviceIdFromDetection != _currentDeviceId.value) {
|
||||||
|
Log.i(TAG, "Device ID found in landmark data: $deviceIdFromDetection")
|
||||||
|
_currentDeviceId.value = deviceIdFromDetection
|
||||||
|
}
|
||||||
|
deviceIdFromDetection
|
||||||
|
} else {
|
||||||
|
// Fallback to cached device ID
|
||||||
|
val cached = _currentDeviceId.value
|
||||||
|
if (cached == "unknown") {
|
||||||
|
Log.w(TAG, "No device_id in landmark data and cached value is 'unknown'")
|
||||||
|
}
|
||||||
|
cached
|
||||||
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Converting ${detections.size} BeeDetections to DetectionEntities with deviceId: $deviceId")
|
Log.d(TAG, "Converting ${detections.size} BeeDetections to DetectionEntities with deviceId: $deviceId")
|
||||||
|
|
||||||
val entities = detections.map { d ->
|
val entities = detections.map { d ->
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ class ForwardingService : LifecycleService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applySettings(s: VarroaSettings) {
|
private fun applySettings(s: VarroaSettings) {
|
||||||
beeClient.updateUrls(s.beeApiUrl, s.beeAltApiUrl)
|
beeClient.updateUrl(s.beeApiUrl)
|
||||||
adamapsClient.updateConfig(s.adamapsApiUrl, s.adamapsApiKey)
|
adamapsClient.updateConfig(s.adamapsApiUrl, s.adamapsApiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,13 +195,9 @@ class ForwardingService : LifecycleService() {
|
||||||
currentBackoffMs = MIN_BACKOFF_MS
|
currentBackoffMs = MIN_BACKOFF_MS
|
||||||
_lastError.value = null
|
_lastError.value = null
|
||||||
|
|
||||||
// Show which IP we're connected to
|
// Show connection status
|
||||||
val activeUrl = beeClient.getActiveUrl()
|
val apiUrl = beeClient.getApiUrl()
|
||||||
_beeStatus.value = if (activeUrl?.contains("155") == true) {
|
_beeStatus.value = "Connected"
|
||||||
"Connected (home WiFi)"
|
|
||||||
} else {
|
|
||||||
"Connected (AP mode)"
|
|
||||||
}
|
|
||||||
|
|
||||||
val newDetections = result.data.filter { d ->
|
val newDetections = result.data.filter { d ->
|
||||||
val key = d.dedupKey()
|
val key = d.dedupKey()
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ fun SettingsScreen(
|
||||||
|
|
||||||
// Local edit state — initialized from current settings
|
// Local edit state — initialized from current settings
|
||||||
var beeApiUrl by remember(currentSettings) { mutableStateOf(currentSettings.beeApiUrl) }
|
var beeApiUrl by remember(currentSettings) { mutableStateOf(currentSettings.beeApiUrl) }
|
||||||
var beeAltApiUrl by remember(currentSettings) { mutableStateOf(currentSettings.beeAltApiUrl) }
|
|
||||||
var adamapsApiUrl by remember(currentSettings) { mutableStateOf(currentSettings.adamapsApiUrl) }
|
var adamapsApiUrl by remember(currentSettings) { mutableStateOf(currentSettings.adamapsApiUrl) }
|
||||||
var adamapsApiKey by remember(currentSettings) { mutableStateOf(currentSettings.adamapsApiKey) }
|
var adamapsApiKey by remember(currentSettings) { mutableStateOf(currentSettings.adamapsApiKey) }
|
||||||
var pollInterval by remember(currentSettings) { mutableStateOf(currentSettings.pollIntervalSeconds.toString()) }
|
var pollInterval by remember(currentSettings) { mutableStateOf(currentSettings.pollIntervalSeconds.toString()) }
|
||||||
|
|
@ -73,7 +72,6 @@ fun SettingsScreen(
|
||||||
vm.save(
|
vm.save(
|
||||||
VarroaSettings(
|
VarroaSettings(
|
||||||
beeApiUrl = beeApiUrl.trim(),
|
beeApiUrl = beeApiUrl.trim(),
|
||||||
beeAltApiUrl = beeAltApiUrl.trim(),
|
|
||||||
adamapsApiUrl = adamapsApiUrl.trim(),
|
adamapsApiUrl = adamapsApiUrl.trim(),
|
||||||
adamapsApiKey = adamapsApiKey.trim(),
|
adamapsApiKey = adamapsApiKey.trim(),
|
||||||
pollIntervalSeconds = pollInterval.toIntOrNull() ?: 30,
|
pollIntervalSeconds = pollInterval.toIntOrNull() ?: 30,
|
||||||
|
|
@ -98,28 +96,11 @@ fun SettingsScreen(
|
||||||
) {
|
) {
|
||||||
SettingsSection("BEE DEVICE") {
|
SettingsSection("BEE DEVICE") {
|
||||||
SettingsField(
|
SettingsField(
|
||||||
label = "Bee API URL (Primary)",
|
label = "Bee API URL",
|
||||||
value = beeApiUrl,
|
value = beeApiUrl,
|
||||||
onValueChange = { beeApiUrl = it },
|
onValueChange = { beeApiUrl = it },
|
||||||
hint = "http://192.168.0.10:5000"
|
hint = "http://192.168.0.10:5000"
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
SettingsField(
|
|
||||||
label = "Bee Alt URL (Home WiFi)",
|
|
||||||
value = beeAltApiUrl,
|
|
||||||
onValueChange = { beeAltApiUrl = it },
|
|
||||||
hint = "http://192.168.197.1:5000"
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
Text(
|
|
||||||
"Primary = Bee's own AP (192.168.0.10)\n" +
|
|
||||||
"Alt = Home WiFi IP when Bee is docked\n" +
|
|
||||||
"App tries both automatically",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
fontSize = 10.sp,
|
|
||||||
lineHeight = 16.sp
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsSection("ADAMAPS") {
|
SettingsSection("ADAMAPS") {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ class DashboardViewModel(app: Application) : AndroidViewModel(app) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applySettings(s: VarroaSettings) {
|
private fun applySettings(s: VarroaSettings) {
|
||||||
beeClient.updateUrls(s.beeApiUrl, s.beeAltApiUrl)
|
beeClient.updateUrl(s.beeApiUrl)
|
||||||
|
|
||||||
// Bind to Bee network if available
|
// Bind to Bee network if available
|
||||||
val beeNet = networkMonitor.getBeeNetworkForBinding()
|
val beeNet = networkMonitor.getBeeNetworkForBinding()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
<network-security-config>
|
<network-security-config>
|
||||||
<domain-config cleartextTrafficPermitted="true">
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
<domain includeSubdomains="true">192.168.0.10</domain>
|
<domain includeSubdomains="true">192.168.0.10</domain>
|
||||||
<domain includeSubdomains="true">192.168.0.10</domain>
|
|
||||||
</domain-config>
|
</domain-config>
|
||||||
<base-config cleartextTrafficPermitted="false" />
|
<base-config cleartextTrafficPermitted="false" />
|
||||||
</network-security-config>
|
</network-security-config>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue