feat: implement telemetry persistence, data visualization, and improved bluetooth stream parsing with frame statistics

This commit is contained in:
2026-06-11 23:59:42 +01:00
parent c95dbfe58e
commit 29423f6aff
26 changed files with 1794 additions and 935 deletions
@@ -0,0 +1,13 @@
package com.example.esp32aldldashboard.data.database
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "sessions")
data class SessionEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val startTime: Long,
val endTime: Long? = null,
val name: String = "",
val isSimulation: Boolean = false
)
@@ -0,0 +1,28 @@
package com.example.esp32aldldashboard.data.database
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface TelemetryDao {
@Insert
suspend fun insertSession(session: SessionEntity): Long
@Query("UPDATE sessions SET endTime = :endTime WHERE id = :sessionId")
suspend fun endSession(sessionId: Long, endTime: Long)
@Insert
suspend fun insertDataPoints(dataPoints: List<TelemetryDataPointEntity>)
@Query("SELECT * FROM sessions ORDER BY startTime DESC")
fun getAllSessions(): Flow<List<SessionEntity>>
@Query("SELECT * FROM telemetry_data_points WHERE sessionId = :sessionId ORDER BY timestamp ASC")
fun getSessionData(sessionId: Long): Flow<List<TelemetryDataPointEntity>>
@Query("DELETE FROM sessions WHERE id = :sessionId")
suspend fun deleteSession(sessionId: Long)
}
@@ -0,0 +1,47 @@
package com.example.esp32aldldashboard.data.database
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "telemetry_data_points",
foreignKeys = [
ForeignKey(
entity = SessionEntity::class,
parentColumns = ["id"],
childColumns = ["sessionId"],
onDelete = ForeignKey.CASCADE
)
],
indices = [
Index(value = ["sessionId", "timestamp"])
]
)
data class TelemetryDataPointEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val sessionId: Long,
val timestamp: Long,
val rawBytes: ByteArray
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TelemetryDataPointEntity
if (id != other.id) return false
if (sessionId != other.sessionId) return false
if (timestamp != other.timestamp) return false
return rawBytes.contentEquals(other.rawBytes)
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + sessionId.hashCode()
result = 31 * result + timestamp.hashCode()
result = 31 * result + rawBytes.contentHashCode()
return result
}
}
@@ -0,0 +1,29 @@
package com.example.esp32aldldashboard.data.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [SessionEntity::class, TelemetryDataPointEntity::class], version = 1, exportSchema = false)
abstract class TelemetryDatabase : RoomDatabase() {
abstract fun telemetryDao(): TelemetryDao
companion object {
@Volatile
private var INSTANCE: TelemetryDatabase? = null
fun getDatabase(context: Context): TelemetryDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
TelemetryDatabase::class.java,
"telemetry_database"
).build()
INSTANCE = instance
instance
}
}
}
}