v5: restore WiFi routing fix, fix send queue
FIXES: - Restored port 5000 (Bee's odc-api runs on 5000, not 5001) - Restored multi-IP discovery (primary: Bee AP, alt: home WiFi) - Added custom DNS resolver for ADAMaps (hardcodes 142.44.213.229) This fixes the '0 sent' bug - DNS fails on Bee AP (no upstream) - Restored alt URL field in settings - Kept exponential backoff and retry queue from v3 The DNS fix is the key: when connected to Bee's AP (192.168.0.10), Android's DNS resolver can't resolve api.adamaps.org because the AP has no internet. Custom Dns object bypasses this.
This commit is contained in:
parent
3cac92fbfd
commit
2b8c19eef1
7 changed files with 217 additions and 34 deletions
|
|
@ -12,8 +12,8 @@ android {
|
|||
applicationId = "com.adamaps.varroa"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 4
|
||||
versionName = "1.3.0"
|
||||
versionCode = 5
|
||||
versionName = "1.4.0"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
|
|
|
|||
|
|
@ -5,18 +5,41 @@ import com.adamaps.varroa.data.ApiResult
|
|||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Dns
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Custom DNS resolver that hardcodes api.adamaps.org to bypass Android's DNS
|
||||
* when connected to Bee's AP (which has no upstream DNS server).
|
||||
*/
|
||||
private object AdaMapsDns : Dns {
|
||||
// api.adamaps.org resolves to this IP
|
||||
private val ADAMAPS_IP = "142.44.213.229"
|
||||
|
||||
override fun lookup(hostname: String): List<InetAddress> {
|
||||
return if (hostname.equals("api.adamaps.org", ignoreCase = true)) {
|
||||
// Return hardcoded IP to bypass DNS lookup
|
||||
listOf(InetAddress.getByName(ADAMAPS_IP))
|
||||
} else {
|
||||
// Fall back to system DNS for other hosts
|
||||
Dns.SYSTEM.lookup(hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AdaMapsApiClient(
|
||||
private var apiUrl: String = "https://api.adamaps.org",
|
||||
private var apiKey: String = "mapnet-ingest-2026"
|
||||
) {
|
||||
|
||||
// Use custom DNS resolver to handle Bee AP's lack of upstream DNS
|
||||
private val client = OkHttpClient.Builder()
|
||||
.dns(AdaMapsDns)
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
|
|
|
|||
|
|
@ -11,28 +11,40 @@ import com.adamaps.varroa.data.GnssData
|
|||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class BeeApiClient(
|
||||
private var baseUrl: String = "http://192.168.0.10:5001"
|
||||
private var primaryUrl: String = "http://192.168.0.10:5000",
|
||||
private var altUrl: String = "http://192.168.0.155:5000"
|
||||
) {
|
||||
|
||||
private var client = buildClient(null)
|
||||
private var fastClient = buildFastClient(null)
|
||||
private val gson = Gson()
|
||||
|
||||
// Track which URL is currently active (null = unknown/offline)
|
||||
private var activeUrl: String? = null
|
||||
|
||||
// Connection state
|
||||
var isConnected: Boolean = false
|
||||
private set
|
||||
|
||||
fun updateBaseUrl(url: String) {
|
||||
baseUrl = url.trimEnd('/')
|
||||
fun updateUrls(primary: String, alt: String) {
|
||||
primaryUrl = primary.trimEnd('/')
|
||||
altUrl = alt.trimEnd('/')
|
||||
// Reset active URL when settings change
|
||||
activeUrl = null
|
||||
}
|
||||
|
||||
fun bindToWifiNetwork(context: Context) {
|
||||
client = buildClient(getWifiNetwork(context))
|
||||
val net = getWifiNetwork(context)
|
||||
client = buildClient(net)
|
||||
fastClient = buildFastClient(net)
|
||||
}
|
||||
|
||||
private fun buildClient(net: Network?): OkHttpClient {
|
||||
|
|
@ -44,6 +56,15 @@ class BeeApiClient(
|
|||
return b.build()
|
||||
}
|
||||
|
||||
private fun buildFastClient(net: Network?): OkHttpClient {
|
||||
val b = OkHttpClient.Builder()
|
||||
.connectTimeout(3, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
net?.let { b.socketFactory(it.socketFactory) }
|
||||
return b.build()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun getWifiNetwork(context: Context): Network? {
|
||||
return try {
|
||||
|
|
@ -58,25 +79,27 @@ class BeeApiClient(
|
|||
} catch (e: Exception) { null }
|
||||
}
|
||||
|
||||
private suspend fun getRaw(path: String): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||
private suspend fun getRaw(path: String, useActiveUrl: Boolean = true): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||
val baseUrl = if (useActiveUrl && activeUrl != null) activeUrl!! else primaryUrl
|
||||
try {
|
||||
val req = Request.Builder().url("$baseUrl$path").get().build()
|
||||
client.newCall(req).execute().use { resp ->
|
||||
val body = resp.body?.string() ?: ""
|
||||
if (resp.isSuccessful) {
|
||||
isConnected = true
|
||||
activeUrl = baseUrl
|
||||
ApiResult.Success(body)
|
||||
} else {
|
||||
ApiResult.Error("HTTP ${resp.code}: ${resp.message}", resp.code)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
isConnected = false
|
||||
ApiResult.Error(e.message ?: "Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getBytes(path: String): ApiResult<ByteArray> = withContext(Dispatchers.IO) {
|
||||
val baseUrl = activeUrl ?: primaryUrl
|
||||
try {
|
||||
val req = Request.Builder().url("$baseUrl$path").get().build()
|
||||
client.newCall(req).execute().use { resp ->
|
||||
|
|
@ -89,12 +112,95 @@ class BeeApiClient(
|
|||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
isConnected = false
|
||||
ApiResult.Error(e.message ?: "Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to ping the Bee at the given URL with a short timeout.
|
||||
* Returns the URL if successful, null otherwise.
|
||||
*/
|
||||
private suspend fun tryPing(url: String): String? = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val req = Request.Builder().url("$url/api/1/info").get().build()
|
||||
fastClient.newCall(req).execute().use { resp ->
|
||||
if (resp.isSuccessful) url else null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
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) {
|
||||
val result = tryPing(activeUrl!!)
|
||||
if (result != null) {
|
||||
isConnected = true
|
||||
return@withContext result
|
||||
}
|
||||
// Active URL failed, clear it and try both
|
||||
activeUrl = null
|
||||
}
|
||||
|
||||
// Try both URLs in parallel
|
||||
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) {
|
||||
altDeferred.cancel()
|
||||
return@withTimeoutOrNull primary
|
||||
}
|
||||
altDeferred.await()
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
activeUrl = result
|
||||
isConnected = true
|
||||
} else {
|
||||
isConnected = false
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Bee is reachable (with discovery fallback).
|
||||
* Updates internal connection state.
|
||||
*/
|
||||
suspend fun ping(): Boolean {
|
||||
val url = discoverActiveUrl()
|
||||
return url != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently active URL (or null if offline).
|
||||
*/
|
||||
fun getActiveUrl(): String? = activeUrl
|
||||
|
||||
/**
|
||||
* Force offline state (for exponential backoff scenarios).
|
||||
*/
|
||||
fun setOffline() {
|
||||
isConnected = false
|
||||
activeUrl = null
|
||||
}
|
||||
|
||||
suspend fun getLandmarks(): ApiResult<List<BeeDetection>> = withContext(Dispatchers.IO) {
|
||||
// If no active URL, try discovery first
|
||||
if (activeUrl == null) {
|
||||
if (discoverActiveUrl() == null) {
|
||||
isConnected = false
|
||||
return@withContext ApiResult.Error("Bee offline")
|
||||
}
|
||||
}
|
||||
|
||||
when (val r = getRaw("/api/1/landmarks/last/200")) {
|
||||
is ApiResult.Success -> try {
|
||||
val type = object : TypeToken<List<BeeDetection>>() {}.type
|
||||
|
|
@ -105,13 +211,24 @@ class BeeApiClient(
|
|||
ApiResult.Error("Parse error: ${e.message}")
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
isConnected = false
|
||||
// Connection failed, clear active URL for next attempt
|
||||
if (r.message.contains("timeout", ignoreCase = true) ||
|
||||
r.message.contains("connect", ignoreCase = true) ||
|
||||
r.message.contains("refused", ignoreCase = true) ||
|
||||
r.message.contains("unreachable", ignoreCase = true)) {
|
||||
activeUrl = null
|
||||
isConnected = false
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")) {
|
||||
is ApiResult.Success -> try {
|
||||
ApiResult.Success(gson.fromJson(r.data, GnssData::class.java))
|
||||
|
|
@ -123,6 +240,10 @@ class BeeApiClient(
|
|||
}
|
||||
|
||||
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")) {
|
||||
is ApiResult.Success -> try {
|
||||
ApiResult.Success(gson.fromJson(r.data, BeeDeviceInfo::class.java))
|
||||
|
|
@ -135,13 +256,23 @@ class BeeApiClient(
|
|||
|
||||
/**
|
||||
* Try the given endpoint; returns raw image bytes.
|
||||
* The caller is responsible for trying fallback endpoints.
|
||||
*/
|
||||
suspend fun getCameraFrame(endpoint: String): ApiResult<ByteArray> = getBytes(endpoint)
|
||||
suspend fun getCameraFrame(endpoint: String): ApiResult<ByteArray> {
|
||||
if (activeUrl == null && discoverActiveUrl() == null) {
|
||||
return ApiResult.Error("Bee offline")
|
||||
}
|
||||
return getBytes(endpoint)
|
||||
}
|
||||
|
||||
/**
|
||||
* Try multiple camera endpoints in order, return first success.
|
||||
*/
|
||||
suspend fun getCameraFrameAuto(configured: String): Pair<String, ApiResult<ByteArray>> {
|
||||
if (activeUrl == null && discoverActiveUrl() == null) {
|
||||
return configured to ApiResult.Error("Bee offline")
|
||||
}
|
||||
|
||||
val candidates = listOf(
|
||||
configured,
|
||||
"/api/1/camera/frame",
|
||||
|
|
@ -155,6 +286,4 @@ class BeeApiClient(
|
|||
}
|
||||
return configured to ApiResult.Error("No camera endpoint responded")
|
||||
}
|
||||
|
||||
suspend fun ping(): Boolean = getDeviceInfo() is ApiResult.Success
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.adamaps.varroa.data
|
|||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
|
|
@ -13,44 +14,52 @@ import kotlinx.coroutines.flow.map
|
|||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "varroa_settings")
|
||||
|
||||
data class VarroaSettings(
|
||||
val beeApiUrl: String = "http://192.168.0.10:5001",
|
||||
val beeApiUrl: String = "http://192.168.0.10:5000",
|
||||
val beeAltApiUrl: String = "http://192.168.0.155:5000",
|
||||
val adamapsApiUrl: String = "https://api.adamaps.org",
|
||||
val adamapsApiKey: String = "mapnet-ingest-2026",
|
||||
val pollIntervalSeconds: Int = 30,
|
||||
val cameraEndpoint: String = "/api/1/camera/frame",
|
||||
val cameraRefreshSeconds: Int = 30,
|
||||
val cameraEndpoint: String = "/api/1/camera/frame"
|
||||
val forwardingEnabled: Boolean = true
|
||||
)
|
||||
|
||||
class SettingsDataStore(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
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_KEY = stringPreferencesKey("adamaps_api_key")
|
||||
private val KEY_POLL_INTERVAL = intPreferencesKey("poll_interval_seconds")
|
||||
private val KEY_CAMERA_REFRESH = intPreferencesKey("camera_refresh_seconds")
|
||||
private val KEY_CAMERA_ENDPOINT = stringPreferencesKey("camera_endpoint")
|
||||
private val KEY_CAMERA_REFRESH = intPreferencesKey("camera_refresh_seconds")
|
||||
private val KEY_FORWARDING_ENABLED = booleanPreferencesKey("forwarding_enabled")
|
||||
}
|
||||
|
||||
val settings: Flow<VarroaSettings> = context.dataStore.data.map { prefs ->
|
||||
VarroaSettings(
|
||||
beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5001",
|
||||
beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5000",
|
||||
beeAltApiUrl = prefs[KEY_BEE_ALT_URL] ?: "http://192.168.0.155:5000",
|
||||
adamapsApiUrl = prefs[KEY_ADAMAPS_URL] ?: "https://api.adamaps.org",
|
||||
adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "mapnet-ingest-2026",
|
||||
pollIntervalSeconds = prefs[KEY_POLL_INTERVAL] ?: 30,
|
||||
cameraEndpoint = prefs[KEY_CAMERA_ENDPOINT] ?: "/api/1/camera/frame",
|
||||
cameraRefreshSeconds = prefs[KEY_CAMERA_REFRESH] ?: 30,
|
||||
cameraEndpoint = prefs[KEY_CAMERA_ENDPOINT] ?: "/api/1/camera/frame"
|
||||
forwardingEnabled = prefs[KEY_FORWARDING_ENABLED] ?: true
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun save(s: VarroaSettings) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[KEY_BEE_URL] = s.beeApiUrl
|
||||
prefs[KEY_BEE_ALT_URL] = s.beeAltApiUrl
|
||||
prefs[KEY_ADAMAPS_URL] = s.adamapsApiUrl
|
||||
prefs[KEY_ADAMAPS_KEY] = s.adamapsApiKey
|
||||
prefs[KEY_POLL_INTERVAL] = s.pollIntervalSeconds
|
||||
prefs[KEY_CAMERA_REFRESH] = s.cameraRefreshSeconds
|
||||
prefs[KEY_CAMERA_ENDPOINT] = s.cameraEndpoint
|
||||
prefs[KEY_CAMERA_REFRESH] = s.cameraRefreshSeconds
|
||||
prefs[KEY_FORWARDING_ENABLED] = s.forwardingEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class ForwardingService : LifecycleService() {
|
|||
}
|
||||
|
||||
private fun applySettings(s: VarroaSettings) {
|
||||
beeClient.updateBaseUrl(s.beeApiUrl)
|
||||
beeClient.updateUrls(s.beeApiUrl, s.beeAltApiUrl)
|
||||
adamapsClient.updateConfig(s.adamapsApiUrl, s.adamapsApiKey)
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +187,14 @@ class ForwardingService : LifecycleService() {
|
|||
consecutiveFailures = 0
|
||||
currentBackoffMs = MIN_BACKOFF_MS
|
||||
_lastError.value = null
|
||||
_beeStatus.value = "Connected"
|
||||
|
||||
// Show which IP we're connected to
|
||||
val activeUrl = beeClient.getActiveUrl()
|
||||
_beeStatus.value = if (activeUrl?.contains("155") == true) {
|
||||
"Connected (home WiFi)"
|
||||
} else {
|
||||
"Connected (AP mode)"
|
||||
}
|
||||
|
||||
val newDetections = result.data.filter { d ->
|
||||
val key = d.dedupKey()
|
||||
|
|
@ -213,15 +220,19 @@ class ForwardingService : LifecycleService() {
|
|||
MAX_BACKOFF_MS
|
||||
)
|
||||
|
||||
// Clean offline status
|
||||
_beeStatus.value = "Bee offline (retry in ${currentBackoffMs / 1000}s)"
|
||||
// Don't show red error banner for connection timeouts
|
||||
if (!result.message.contains("timeout", ignoreCase = true) &&
|
||||
!result.message.contains("connect", ignoreCase = true) &&
|
||||
!result.message.contains("refused", ignoreCase = true)) {
|
||||
_lastError.value = result.message
|
||||
} else {
|
||||
// Set clean offline status instead of ugly error
|
||||
if (result.message == "Bee offline") {
|
||||
_beeStatus.value = "Bee offline (retry in ${currentBackoffMs / 1000}s)"
|
||||
_lastError.value = null // Don't show red error for expected offline state
|
||||
} else if (result.message.contains("timeout", ignoreCase = true) ||
|
||||
result.message.contains("connect", ignoreCase = true) ||
|
||||
result.message.contains("refused", ignoreCase = true) ||
|
||||
result.message.contains("unreachable", ignoreCase = true)) {
|
||||
_beeStatus.value = "Bee offline (retry in ${currentBackoffMs / 1000}s)"
|
||||
_lastError.value = null
|
||||
} else {
|
||||
_beeStatus.value = "Connection error"
|
||||
_lastError.value = result.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ fun SettingsScreen(
|
|||
|
||||
// Local edit state — initialized from current settings
|
||||
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 adamapsApiKey by remember(currentSettings) { mutableStateOf(currentSettings.adamapsApiKey) }
|
||||
var pollInterval by remember(currentSettings) { mutableStateOf(currentSettings.pollIntervalSeconds.toString()) }
|
||||
|
|
@ -72,6 +73,7 @@ fun SettingsScreen(
|
|||
vm.save(
|
||||
VarroaSettings(
|
||||
beeApiUrl = beeApiUrl.trim(),
|
||||
beeAltApiUrl = beeAltApiUrl.trim(),
|
||||
adamapsApiUrl = adamapsApiUrl.trim(),
|
||||
adamapsApiKey = adamapsApiKey.trim(),
|
||||
pollIntervalSeconds = pollInterval.toIntOrNull() ?: 30,
|
||||
|
|
@ -96,15 +98,23 @@ fun SettingsScreen(
|
|||
) {
|
||||
SettingsSection("BEE DEVICE") {
|
||||
SettingsField(
|
||||
label = "Bee API URL",
|
||||
label = "Bee API URL (Primary)",
|
||||
value = beeApiUrl,
|
||||
onValueChange = { beeApiUrl = it },
|
||||
hint = "http://192.168.0.10:5001"
|
||||
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.0.155:5000"
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Port 5001 = socat proxy to odc-api\n" +
|
||||
"Must run proxy setup on Bee first",
|
||||
"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,
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ class DashboardViewModel(app: Application) : AndroidViewModel(app) {
|
|||
}
|
||||
|
||||
private fun applySettings(s: VarroaSettings) {
|
||||
beeClient.updateBaseUrl(s.beeApiUrl)
|
||||
beeClient.updateUrls(s.beeApiUrl, s.beeAltApiUrl)
|
||||
beeClient.bindToWifiNetwork(getApplication())
|
||||
}
|
||||
|
||||
private fun startPolling(s: VarroaSettings) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue