Skip to content
This repository has been archived by the owner on Aug 19, 2023. It is now read-only.

Commit

Permalink
Fixed gear update behavior when the device is reconnecting due to pow…
Browse files Browse the repository at this point in the history
…er loss.

DeviceName changes, so we need to map to existing Gear via serial number.
Previously we mapped against the deviceName which stays only the same if USB is reconnected, but the gear didn't loose power.
  • Loading branch information
d4rken committed May 18, 2021
1 parent 5a2415d commit a42d19c
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 36 deletions.
11 changes: 10 additions & 1 deletion app/src/main/java/eu/darken/fpv/dvca/gear/Gear.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ interface Gear {
val identifier: String
get() = device.identifier

val serialNumber: HWDevice.SerialNumber?
get() = device.serialNumber

val label: String
get() = device.label
get() = gearName

val gearName: String
get() = device.productName

val isGearConnected: Boolean

val logId: String
get() = "$serialNumber § $identifier $gearName"

val events: Flow<Event>

sealed interface Event {
Expand Down
50 changes: 34 additions & 16 deletions app/src/main/java/eu/darken/fpv/dvca/gear/GearManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class GearManager @Inject constructor(
) {

private val mutex = Mutex()
private val gearMap = mutableMapOf<String, Gear>()
private val gearMap = mutableMapOf<HWDevice.SerialNumber, Gear>()
private val factories = setOf<Gear.Factory>(
fpvGogglesV1Factory
)
Expand All @@ -35,30 +35,48 @@ class GearManager @Inject constructor(
Timber.tag(TAG).v("Devices changed! Updating gearmap.")
updateGearMap(devices)
}
.catch { Timber.tag(TAG).e("Failed to handle device status change.") }
.catch { Timber.tag(TAG).e(it, "Failed to handle device status change.") }
.launchIn(appScope)
}

private suspend fun updateGearMap(devices: Set<HWDevice>) = mutex.withLock {
Timber.tag(TAG).v("Updating gear...")

gearMap.values.forEach { gear ->
val match = devices.singleOrNull { it.identifier == gear.identifier }
gear.updateDevice(match)
val gears = devices.mapNotNull { device ->
val gear = device.createGear()
if (gear == null) {
Timber.tag(TAG).d("Unknown device, couldn't create gear: %s", device.logId)
return@mapNotNull null
}

Timber.tag(TAG).i("Known type of gear: %s", device.logId)

if (!device.hasPermission) {
// Need to be able to read the serial
Timber.tag(TAG).d("Missing usb permission, requesting access now")
deviceManager.requestPermission(device)
}

gear
}

devices.forEach { device ->
// Was already updated
if (gearMap.containsKey(device.identifier)) return@forEach
gearMap.values.forEach { existing ->
val match = gears.singleOrNull { it.serialNumber == existing.serialNumber }
Timber.tag(TAG).v("Updating gear %s", existing.logId)
existing.updateDevice(match?.device)
}

val newGear = device.createGear()
if (newGear == null) {
Timber.tag(TAG).d("Unknown device, couldn't create gear: %s", device.label)
gears.forEach { gear ->
val snOfNewGear = gear.serialNumber
if (snOfNewGear == null) {
Timber.tag(TAG).w("Serial number was unavailable, no permission? Can't add %s", gear.logId)
return@forEach
} else {
Timber.tag(TAG).i("Added new gear: %s", device.label)
}
gearMap[newGear.identifier] = newGear

// Was already updated
if (gearMap.containsKey(snOfNewGear)) return@forEach

gearMap[snOfNewGear] = gear
}

Timber.tag(TAG).v("...gear update done.")
Expand All @@ -70,11 +88,11 @@ class GearManager @Inject constructor(
private fun HWDevice.createGear(): Gear? {
val factory = this.findFactory()
if (factory == null) {
Timber.tag(TAG).i("No gear factory to handle device: %s", this.label)
Timber.tag(TAG).i("No gear factory to handle device: %s", this.identifier)
return null
}
return factory.create(this@GearManager, this).also {
Timber.tag(TAG).i("Used %s to create %s", factory, it.label)
Timber.tag(TAG).i("Used %s to create %s", factory, it)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,20 @@ class FpvGogglesV1(

val reconnect = device != null && currentDevice == null

currentDevice = device

if (device == null) {
Timber.tag(TAG).w("Device disconnected!")

val wasActive = videoFeedInternal.value != null
val wasActive = videoFeedInternal.value != null || wasVideoActive
if (wasActive) {
Timber.tag(TAG).d("Video feed was active on disconnect, will restart after reconnect.")
}

stopVideoFeed()

wasVideoActive = wasActive

eventsInternal.value = Gear.Event.GearDetached(this)
} else if (reconnect) {
Timber.tag(TAG).w("Device reconnected!")
Expand All @@ -54,10 +58,10 @@ class FpvGogglesV1(
if (wasVideoActive) {
Timber.tag(TAG).i("Video feed was previously active, starting again.")
startVideoFeed()
} else {
Timber.tag(TAG).v("Video was previously not active.")
}
}

currentDevice = device
}

private val videoFeedInternal = MutableStateFlow<Goggles.VideoFeed?>(null)
Expand Down
26 changes: 19 additions & 7 deletions app/src/main/java/eu/darken/fpv/dvca/usb/HWDevice.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,33 @@ class HWDevice(
val rawDevice: UsbDevice,
) {
private val mutex = Mutex()
private var storedSerial: String? = null
val serialNumber: SerialNumber?
get() {
val number = storedSerial ?: try {
rawDevice.serialNumber?.also { storedSerial = it }
} catch (e: SecurityException) {
null
}

return number?.let { SerialNumber(it) }
}

val identifier: String
get() = rawDevice.deviceName

val label: String
get() = "${rawDevice.manufacturerName} \"${rawDevice.productName}\" (${rawDevice.deviceName})"
val productName: String
get() = rawDevice.productName ?: rawDevice.manufacturerName ?: rawDevice.deviceName

val hasPermission: Boolean
get() = hwManager.hasPermission(this)

private suspend fun requirePermission() {
if (!hasPermission) hwManager.requestPermission(this)
}
val logId: String
get() = "${rawDevice.deviceName} ($productName)"

private val openConnections = mutableSetOf<HWConnection>()

suspend fun openConnection(): HWConnection = mutex.withLock {
requirePermission()
val connection = hwManager.openDevice(this)
openConnections.add(connection)
return connection
Expand All @@ -41,9 +50,12 @@ class HWDevice(
openConnections.forEach { it.close() }
}

override fun toString(): String = label
override fun toString(): String = logId

companion object {
private val TAG = App.logTag("Usb", "Device")
}

@JvmInline
value class SerialNumber(val serialNumber: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class HWDeviceManager @Inject constructor(
) {
Timber.tag(TAG).v("onDeviceAttached(rawDevice=%s)", rawDevice.label)

val added = usbManager.find(rawDevice.identifier) ?: throw UnknownDeviceException(rawDevice)
val added = rawDevice.toHWDevice()

this[rawDevice.identifier]?.let {
Timber.tag(TAG).w("Double attach? Releasing previous device: %s", it)
Expand All @@ -64,7 +64,7 @@ class HWDeviceManager @Inject constructor(
Timber.tag(TAG).w("Failed to remove %s, already removed?", rawDevice.label)
return@updateAsync this
} else {
Timber.tag(TAG).i("Removing detached devices %s", toRemove.label)
Timber.tag(TAG).i("Removing detached devices %s", toRemove.logId)
toRemove.release()
}

Expand All @@ -88,15 +88,22 @@ class HWDeviceManager @Inject constructor(
return HWConnection(
rawDevice = device.rawDevice,
rawConnection = rawConnection
).also { Timber.tag(TAG).d("Connection to %s opened: %s", device.label, it) }
).also { Timber.tag(TAG).d("Connection to %s opened: %s", device.logId, it) }
}

fun hasPermission(device: HWDevice): Boolean {
return usbManager.hasPermission(device.rawDevice)
}
fun hasPermission(device: HWDevice): Boolean = usbManager.hasPermission(device.rawDevice)

suspend fun requestPermission(device: HWDevice): Boolean {
return usbPermissionHandler.requestPermission(device)
Timber.tag(TAG).v("Requesting permission for %s", device.logId)

if (hasPermission(device)) {
Timber.tag(TAG).w("Unnecessary permission request, we already have it.")
return true
}

return usbPermissionHandler.requestPermission(device).also {
Timber.tag(TAG).v("Permission request isGranted=$it for %s", device.logId)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ class VideoFeedVM @Inject constructor(
}
.filterNotNull()
.onEach {
Timber.tag(TAG).d("Device available: %s", it.label)
Timber.tag(TAG).d("Device available: %s", it.logId)
if (it.videoFeed.first() == null) {
Timber.tag(TAG).d("Enabling videofeed for %s", it.label)
Timber.tag(TAG).d("Enabling videofeed for %s", it.logId)
it.startVideoFeed()
}
}
Expand Down

0 comments on commit a42d19c

Please sign in to comment.