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

Commit

Permalink
Good draft.
Browse files Browse the repository at this point in the history
  • Loading branch information
d4rken committed May 16, 2021
1 parent 218127c commit 5dfeff2
Show file tree
Hide file tree
Showing 33 changed files with 1,488 additions and 211 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
An unofficial companion app for DJI's Digital FPV System for Android devices.
Plug your Android device into your Googles and watch a live feed on your phone.

If this app doesn't tickle your fancy, maybe [this one](https://github.com/fpvout/DigiView-Android) from @jlucidar floats your boat.
If this app doesn't tickle your fancy, maybe [this one](https://github.com/fpvout/DigiView-Android) from @jlucidar does.

Want to chat? There is a [discord server here](https://discord.gg/q5gHFXAs9e).
Be nice. No is getting paid to do any of this, we all just like FPV.

Thank you:
* @fpv-wtf for jump starting everytjomg by finding the undocumented USB behavior
* @fpv-wtf for jump starting everything by finding the magic USB sauce
* @jlucidar for laying the groundwork with his app
* D3VL nourishing a nice community

Thank me? [Buy me a coffee!](https://www.buymeacoffee.com/tydarken)
[Coffee?](https://www.buymeacoffee.com/tydarken)
8 changes: 6 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ android {
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
"-Xuse-experimental=kotlin.time.ExperimentalTime",
"-Xopt-in=kotlin.RequiresOptIn"
"-Xopt-in=kotlin.RequiresOptIn",
"-Xjvm-default=enable"
]
}
}
Expand All @@ -128,6 +129,9 @@ android {
}

dependencies {
implementation("com.squareup.okio:okio:3.0.0-alpha.4")
implementation 'com.google.android.exoplayer:exoplayer:2.14.0'

// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin.core}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlin.coroutines}"
Expand All @@ -143,7 +147,7 @@ dependencies {

// Debugging
implementation "com.jakewharton.timber:timber:4.7.1"
implementation ('com.bugsnag:bugsnag-android:5.9.2')
implementation('com.bugsnag:bugsnag-android:5.9.2')
implementation 'com.getkeepsafe.relinker:relinker:1.4.3'


Expand Down
26 changes: 15 additions & 11 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.darken.fpv.dvca">

<uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-feature
android:name="android.hardware.usb.host"
android:required="true"/>
android:required="true" />

<application
android:name="eu.darken.fpv.dvca.App"
Expand All @@ -23,19 +25,21 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<!-- <intent-filter>-->
<!-- <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />-->
<!-- </intent-filter>-->

<!-- <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"-->
<!-- android:resource="@xml/usb_device_filter" />-->
</activity>
<service android:name="eu.darken.fpv.dvca.videofeed.core.service.ExampleService" />

<receiver android:name=".usb.core.UsbDeviceEventReceiver">
<intent-filter >
<action android:name="android.hardware.usb.action.USB_STATE"/>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"/>
<receiver android:name=".usb.manager.UsbDeviceEventReceiver">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_STATE" />
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter"/>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"
android:resource="@xml/usb_device_filter"/>
</receiver>
</application>

Expand Down
17 changes: 13 additions & 4 deletions app/src/main/java/eu/darken/fpv/dvca/gear/Gear.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package eu.darken.fpv.dvca.gear

import eu.darken.fpv.dvca.usb.core.DVCADevice
import eu.darken.fpv.dvca.usb.DVCADevice
import kotlinx.coroutines.flow.Flow

sealed interface Gear {
interface Gear {
val device: DVCADevice

val identifier: String
Expand All @@ -13,9 +14,17 @@ sealed interface Gear {

val isGearConnected: Boolean

fun updateDevice(device: DVCADevice?)
val events: Flow<Event>

interface Goggles : Gear
sealed interface Event {
val gear: Gear

data class GearAttached(override val gear: Gear) : Event

data class GearDetached(override val gear: Gear) : Event
}

suspend fun updateDevice(device: DVCADevice?)

interface Factory {
fun canHandle(device: DVCADevice): Boolean
Expand Down
24 changes: 12 additions & 12 deletions app/src/main/java/eu/darken/fpv/dvca/gear/GearManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package eu.darken.fpv.dvca.gear

import eu.darken.fpv.dvca.App
import eu.darken.fpv.dvca.common.coroutine.AppScope
import eu.darken.fpv.dvca.gear.goggles.DjiFpvGogglesV1
import eu.darken.fpv.dvca.usb.core.DVCADevice
import eu.darken.fpv.dvca.usb.core.DVCADeviceManager
import eu.darken.fpv.dvca.gear.goggles.djifpv.FpvGogglesV1
import eu.darken.fpv.dvca.usb.DVCADevice
import eu.darken.fpv.dvca.usb.manager.DVCADeviceManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
Expand All @@ -16,20 +16,20 @@ import javax.inject.Singleton
@Singleton
class GearManager @Inject constructor(
@AppScope private val appScope: CoroutineScope,
private val dvcaDeviceManager: DVCADeviceManager,
djiFpvGogglesV1Factory: DjiFpvGogglesV1.Factory
private val deviceManager: DVCADeviceManager,
fpvGogglesV1Factory: FpvGogglesV1.Factory
) {

private val mutex = Mutex()
private val gearMap = mutableMapOf<String, Gear>()
private val factories = setOf<Gear.Factory>(
djiFpvGogglesV1Factory
fpvGogglesV1Factory
)
private val internalGearFlow = MutableStateFlow(emptySet<Gear>())
val availableGear: Flow<Set<Gear>> = internalGearFlow

init {
dvcaDeviceManager.devices
deviceManager.devices
.onStart { Timber.tag(TAG).d("Observing devices.") }
.onEach { devices ->
Timber.tag(TAG).v("Devices changed! Updating gearmap.")
Expand All @@ -48,20 +48,20 @@ class GearManager @Inject constructor(
}

devices.forEach { device ->
if (gearMap.containsKey(device.identifier)) {
Timber.tag(TAG).v("Skipping because not new gear: %s", device.label)
return@forEach
}
// Was already updated
if (gearMap.containsKey(device.identifier)) return@forEach

val newGear = device.createGear()
if (newGear == null) {
Timber.tag(TAG).d("Unknown device, couldn't create gear: %s", device.label)
return@forEach
} else {
Timber.tag(TAG).i("Added new gear: %s", device.label)
}
gearMap[newGear.identifier] = newGear
}

Timber.tag(TAG).v("... gear update done.")
Timber.tag(TAG).v("...gear update done.")
internalGearFlow.value = gearMap.values.toSet()
}

Expand Down

This file was deleted.

20 changes: 20 additions & 0 deletions app/src/main/java/eu/darken/fpv/dvca/gear/goggles/Goggles.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package eu.darken.fpv.dvca.gear.goggles

import com.google.android.exoplayer2.upstream.DataSource
import eu.darken.fpv.dvca.gear.Gear
import kotlinx.coroutines.flow.Flow

interface Goggles : Gear {

val videoFeed: Flow<VideoFeed?>

suspend fun startVideoFeed(): VideoFeed

suspend fun stopVideoFeed()

interface VideoFeed {
val exoDataSource: DataSource

fun close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package eu.darken.fpv.dvca.gear.goggles.djifpv

import eu.darken.fpv.dvca.App
import eu.darken.fpv.dvca.gear.Gear
import eu.darken.fpv.dvca.gear.GearManager
import eu.darken.fpv.dvca.gear.goggles.Goggles
import eu.darken.fpv.dvca.usb.DVCADevice
import eu.darken.fpv.dvca.usb.connection.DVCAConnection
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import timber.log.Timber
import javax.inject.Inject

class FpvGogglesV1(
private val gearManager: GearManager,
private val initialDevice: DVCADevice,
) : Goggles {
private var currentDevice: DVCADevice? = initialDevice

override val device: DVCADevice
get() = currentDevice ?: initialDevice

override val isGearConnected: Boolean
get() = currentDevice != null

private val eventsInternal = MutableStateFlow<Gear.Event?>(null)
override val events: Flow<Gear.Event>
get() = eventsInternal.filterNotNull()

private var wasVideoActive: Boolean = false

override suspend fun updateDevice(device: DVCADevice?) {
Timber.tag(TAG).d("updateDevice(device=%s)", device)

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

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

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

stopVideoFeed()
eventsInternal.value = Gear.Event.GearDetached(this)
} else if (reconnect) {
Timber.tag(TAG).w("Device reconnected!")

eventsInternal.value = Gear.Event.GearAttached(this)
if (wasVideoActive) {
Timber.tag(TAG).i("Video feed was previously active, starting again.")
startVideoFeed()
}
}

currentDevice = device
}

private val videoFeedInternal = MutableStateFlow<Goggles.VideoFeed?>(null)
override val videoFeed: Flow<Goggles.VideoFeed?> = videoFeedInternal

private var connection: DVCAConnection? = null

override suspend fun startVideoFeed(): Goggles.VideoFeed {
Timber.tag(TAG).i("startVideoFeed()")
videoFeedInternal.value?.let {
Timber.tag(TAG).w("Feed is already active!")
return it
}

connection = device.openConnection()
return FpvGogglesV1VideoFeed(connection!!).also { feed ->
videoFeedInternal.value = feed
}
}

override suspend fun stopVideoFeed() {
Timber.tag(TAG).i("stopVideoFeed()")
videoFeedInternal.value?.close()
videoFeedInternal.value = null

connection?.close()
connection = null

wasVideoActive = true
}

companion object {
private val TAG = App.logTag("Gear", "FpvGogglesV1")
private const val VENDOR_ID = 11427
private const val PRODUCT_ID = 31
}

class Factory @Inject constructor() : Gear.Factory {

override fun canHandle(device: DVCADevice): Boolean = device.rawDevice.run {
vendorId == VENDOR_ID && productId == PRODUCT_ID
}

override fun create(
gearManager: GearManager,
device: DVCADevice
): Goggles = FpvGogglesV1(
gearManager = gearManager,
initialDevice = device,
)
}
}
Loading

0 comments on commit 5dfeff2

Please sign in to comment.