Skip to content
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ dependencies {
implementation(libs.androidx.media3.exoplayer.dash)
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.ui.compose)
implementation(libs.mlkit.face-detection)
implementation("com.github.wendykierp:JTransforms:3.2")

}
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ internal class BackgroundTaskController (private val context: Context): EventLis
}
)
}
"faceStateChanged" -> {
val state = event.newValue as Boolean
server.sendStatus(
buildJsonObject {
putJsonObject("sensors", {
put("motion_detected", state)
})
}
)
}
"lastActivity" -> {
server.sendStatus(
buildJsonObject {
Expand All @@ -281,6 +291,9 @@ internal class BackgroundTaskController (private val context: Context): EventLis
"motionDetectionSensitivity" -> {
motionTask.setSensitivity(event.newValue as Int)
}
"motionDetectionMode" -> {
motionTask.setDetectionMode(event.newValue.toString())
}
else -> consumed = false
}
if (consumed) {
Expand Down Expand Up @@ -476,7 +489,6 @@ internal class BackgroundTaskController (private val context: Context): EventLis
}
}
}

fun sendDiagnostics(audioLevel: Float, detectionLevel: Float) {
if (config.diagnosticsEnabled) {
val data = DiagnosticInfo(
Expand Down Expand Up @@ -507,4 +519,4 @@ internal class BackgroundTaskController (private val context: Context): EventLis
server.stop()

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ import timber.log.Timber
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min

import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.face.FaceDetection
import com.google.mlkit.vision.face.FaceDetectorOptions

class CameraBackgroundTask(val context: Context) {

Expand All @@ -44,7 +46,14 @@ class CameraBackgroundTask(val context: Context) {

private var checkInterval: Long = 500
private var lastCheck: Long = 0
private val detector = AggregateLumaMotionDetection()

// Additional variables required at the top of the class:
private var detectionMode: String = "luma" // Defaults to original method
private val lumaDetector = AggregateLumaMotionDetection()

private val faceDetectorOptions = FaceDetectorOptions.Builder().setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST).build()
private val faceDetector = FaceDetection.getClient(faceDetectorOptions)
private var isFaceCurrentlyDetected = false

// Camera2-related stuff
private var cameraManager: CameraManager? = null
Expand All @@ -70,8 +79,15 @@ class CameraBackgroundTask(val context: Context) {
const val MAX_LENIENCY = 50
}

// Restoring the original sensitivity function:
fun setSensitivity(sensitivity: Int) {
detector.setLeniency(min(MAX_LENIENCY, max(0, MAX_LENIENCY - (sensitivity))))
lumaDetector.setLeniency(min(MAX_LENIENCY, max(0, MAX_LENIENCY - (sensitivity))))
}

// Adding the mode switch function:
fun setDetectionMode(mode: String) {
detectionMode = mode
Timber.d("Camera motion detection mode set to: $detectionMode")
}

fun startCamera() {
Expand Down Expand Up @@ -121,32 +137,58 @@ class CameraBackgroundTask(val context: Context) {


private val imageListener = ImageReader.OnImageAvailableListener { reader ->

val image = reader?.acquireLatestImage()

val now = System.currentTimeMillis()
if (now - lastCheck > checkInterval) {
lastCheck = now

if (image != null) {
if (image != null) {

if (detectionMode == "face") {
// --- NEW ML KIT FACE DETECTION LOGIC ---
val inputImage = InputImage.fromMediaImage(image, 0)
faceDetector.process(inputImage)
.addOnSuccessListener { faces ->
if (settleDelayJob != null && !settleDelayJob?.isActive!!) {
if (faces.isNotEmpty()) {
val currentTime = System.currentTimeMillis()
if (!isFaceCurrentlyDetected) {
isFaceCurrentlyDetected = true
lastDetection = currentTime
log.d("Face detected as motion")
config.eventBroadcaster.notifyEvent(Event("faceStateChanged", "", true))
} else if (currentTime - lastDetection > MOTION_INTERVAL) {
lastDetection = currentTime
log.d("Face still detected, keeping HA sensor alive")
config.eventBroadcaster.notifyEvent(Event("faceStateChanged", "", true))
}
} else {
if (isFaceCurrentlyDetected) {
isFaceCurrentlyDetected = false
log.d("Face no longer detected")
config.eventBroadcaster.notifyEvent(Event("faceStateChanged", "", false))
}
}
}
}
.addOnCompleteListener {
image.close()
}
} else {
// --- ORIGINAL LUMA PIXEL DETECTION LOGIC ---
val buffer = image.planes[0].buffer
buffer.rewind()
val data = ByteArray(buffer.capacity())
buffer.get(data)

val img = ImageProcessing.decodeYUV420SPtoLuma(data, image.width, image.height)
if (settleDelayJob != null && !settleDelayJob?.isActive!!) {
if (detector.detect(img, image.width, image.height)) {
if (lumaDetector.detect(img, image.width, image.height)) {
if (System.currentTimeMillis() - lastDetection > MOTION_INTERVAL) {
log.d("Motion detected")
log.d("Motion detected via luma")
config.eventBroadcaster.notifyEvent(Event("motion", "", ""))
lastDetection = System.currentTimeMillis()
}
}
}
image.close()
}
}
image?.close()
}

private val stateCallback = object : CameraDevice.StateCallback() {
Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ ui = "1.10.4"
webkit = "1.15.0"
camerax = "1.6.0-rc01"
accompanist = "0.37.3"
foundation = "1.10.0"
face-detection = "16.1.7"
foundation = "1.10.4"
litert = "1.4.0"
protobufKotlin = "4.34.0"
Expand Down Expand Up @@ -76,6 +78,7 @@ androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycl
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
mlkit-face-detection = { module = "com.google.mlkit:face-detection", version.ref = "face-detection" }
litert = { module = "com.google.ai.edge.litert:litert", version.ref = "litert" }
protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "protobufKotlin" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
Expand Down