feat: implement telemetry persistence, data visualization, and improved bluetooth stream parsing with frame statistics (#9)
Build and Release APK / build-and-release (push) Successful in 10m7s

Reviewed-on: #9
Co-authored-by: Gandalf <gordon@i3omb.com>
Co-committed-by: Gandalf <gordon@i3omb.com>
This commit was merged in pull request #9.
This commit is contained in:
2026-06-14 08:34:34 +01:00
committed by gronod
parent c95dbfe58e
commit ca3e4d73a5
46 changed files with 4505 additions and 956 deletions
@@ -0,0 +1,98 @@
package com.example.esp32aldldashboard.logging
import android.content.ContentValues
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import java.io.OutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* Logs raw binary ALDL data streams for debugging purposes.
* Records full 27-byte frames (AA 55 header + 25-byte payload) to Downloads/ALDLLogs/
*/
class RawStreamLogger(private val context: Context) {
private var currentOutputStream: OutputStream? = null
private var currentUri: Uri? = null
private var isRecording = false
/**
* Starts a new raw recording session.
* @return true if recording started successfully, false otherwise
*/
fun startRecording(): Boolean {
if (isRecording) return true
val timeStamp: String = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US).format(Date())
val fileName = "raw_$timeStamp.bin"
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver = context.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "application/octet-stream")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/ALDLLogs")
}
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
if (uri != null) {
currentUri = uri
currentOutputStream = resolver.openOutputStream(uri)
isRecording = true
true
} else {
false
}
} else {
// Legacy support not implemented for this debug feature
false
}
} catch (e: Exception) {
e.printStackTrace()
false
}
}
/**
* Logs a single 27-byte frame (including AA 55 header).
* @param frame The complete 27-byte frame to log
*/
fun logFrame(frame: ByteArray) {
if (!isRecording || frame.size != 27) return
try {
currentOutputStream?.write(frame)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* Stops the current recording session and closes the file.
*/
fun stopRecording() {
if (!isRecording) return
try {
currentOutputStream?.flush()
currentOutputStream?.close()
} catch (e: Exception) {
e.printStackTrace()
} finally {
currentOutputStream = null
currentUri = null
isRecording = false
}
}
/**
* Returns whether a recording session is currently active.
*/
fun isRecording(): Boolean = isRecording
}