Compare commits
43 Commits
ca3e4d73a5
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| c9e9fede29 | |||
| 180ed20741 | |||
| b21efd5c99 | |||
| 8bf3d53448 | |||
| 3278fd54b2 | |||
| d034fb1b98 | |||
| 807ac93646 | |||
| a9dec01688 | |||
| 0415ec366a | |||
| c229dc4e64 | |||
| 4b48d455e4 | |||
| b646d8eaa8 | |||
| 63aaffc1d7 | |||
| da8f216edd | |||
| 4855712da5 | |||
| 0387de99c7 | |||
| 4d0ab48c1f | |||
| d40a63ec4a | |||
| 2e94463f37 | |||
| 6a6232649c | |||
| ccfaf2686c | |||
| 621f67aa38 | |||
| df2f851a0d | |||
| 0b43a024f8 | |||
| 877db7f098 | |||
| e5c4e00d76 | |||
| d4c87efe7e | |||
| 4a5379e3a1 | |||
| 5bd096f54b | |||
| 7c2505ab7b | |||
| d8e656284a | |||
| b3c475ad18 | |||
| b06de4e2fe | |||
| 9838378933 | |||
| bb47d61eec | |||
| f760a9f300 | |||
| 05a5acac10 | |||
| 6c57086f25 | |||
| 7653876fed | |||
| 89e2ed4cd0 | |||
| ae997da849 | |||
| 5383c5bd11 | |||
| 29423f6aff |
@@ -0,0 +1,77 @@
|
||||
# Required Gitea secrets:
|
||||
# SIGNING_KEYSTORE_BASE64 - base64-encoded Android release keystore
|
||||
# SIGNING_STORE_PASSWORD - keystore password
|
||||
# SIGNING_KEY_ALIAS - key alias
|
||||
# SIGNING_KEY_PASSWORD - key password
|
||||
name: Build and Release APK
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate tag format
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Tag ${{ github.ref_name }} is valid."
|
||||
else
|
||||
echo "Invalid tag format: ${{ github.ref_name }}. Expected vN.N.N"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Decode keystore
|
||||
run: |
|
||||
echo "${{ secrets.SIGNING_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
|
||||
|
||||
- name: Export signing environment variables
|
||||
run: |
|
||||
echo "SIGNING_STORE_FILE=$(pwd)/release.keystore" >> $GITHUB_ENV
|
||||
echo "SIGNING_STORE_PASSWORD=${{ secrets.SIGNING_STORE_PASSWORD }}" >> $GITHUB_ENV
|
||||
echo "SIGNING_KEY_ALIAS=${{ secrets.SIGNING_KEY_ALIAS }}" >> $GITHUB_ENV
|
||||
echo "SIGNING_KEY_PASSWORD=${{ secrets.SIGNING_KEY_PASSWORD }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Test and Build Release APK
|
||||
run: chmod +x ./gradlew && ./gradlew test assembleRelease
|
||||
|
||||
- name: Rename APK
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
cp app/build/outputs/apk/release/app-release.apk artifacts/esp32-aldl-dashboard-${{ github.ref_name }}.apk
|
||||
|
||||
- name: Create Gitea Release
|
||||
id: create_release
|
||||
run: |
|
||||
response=$(curl -s -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"${{ github.ref_name }}\",\"name\":\"Release ${{ github.ref_name }}\",\"body\":\"Auto-generated release for ${{ github.ref_name }}.\"}" \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases")
|
||||
echo "release_response=$response" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload APK to Gitea Release
|
||||
run: |
|
||||
upload_url=$(echo "$release_response" | grep -o '"url":"[^"]*' | head -1 | cut -d'"' -f4)
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Content-Type: application/vnd.android.package-archive" \
|
||||
--data-binary @artifacts/esp32-aldl-dashboard-${{ github.ref_name }}.apk \
|
||||
"${upload_url}/assets?name=esp32-aldl-dashboard-${{ github.ref_name }}.apk"
|
||||
@@ -1,11 +1,14 @@
|
||||
# Required GitHub secrets:
|
||||
# SIGNING_KEYSTORE_BASE64 - base64-encoded Android release keystore
|
||||
# SIGNING_STORE_PASSWORD - keystore password
|
||||
# SIGNING_KEY_ALIAS - key alias
|
||||
# SIGNING_KEY_PASSWORD - key password
|
||||
name: Build and Release APK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- v2
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
@@ -13,6 +16,15 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Validate tag format
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Tag ${{ github.ref_name }} is valid."
|
||||
else
|
||||
echo "Invalid tag format: ${{ github.ref_name }}. Expected vN.N.N"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -28,29 +40,27 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Make gradlew executable
|
||||
run: chmod +x ./gradlew
|
||||
- name: Decode keystore
|
||||
run: |
|
||||
echo "${{ secrets.SIGNING_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: ./gradlew test
|
||||
- name: Export signing environment variables
|
||||
run: |
|
||||
echo "SIGNING_STORE_FILE=$(pwd)/release.keystore" >> $GITHUB_ENV
|
||||
echo "SIGNING_STORE_PASSWORD=${{ secrets.SIGNING_STORE_PASSWORD }}" >> $GITHUB_ENV
|
||||
echo "SIGNING_KEY_ALIAS=${{ secrets.SIGNING_KEY_ALIAS }}" >> $GITHUB_ENV
|
||||
echo "SIGNING_KEY_PASSWORD=${{ secrets.SIGNING_KEY_PASSWORD }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Debug APK
|
||||
run: ./gradlew assembleDebug
|
||||
|
||||
- name: Get Short SHA
|
||||
id: vars
|
||||
run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
- name: Test and Build Release APK
|
||||
run: chmod +x ./gradlew && ./gradlew test assembleRelease
|
||||
|
||||
- name: Rename APK
|
||||
run: |
|
||||
mv app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/debug/esp32-aldl-dashboard-${{ env.short_sha }}.apk
|
||||
mkdir -p artifacts
|
||||
cp app/build/outputs/apk/release/app-release.apk artifacts/esp32-aldl-dashboard-${{ github.ref_name }}.apk
|
||||
|
||||
- name: Create Gitea Release
|
||||
- name: Create Release and Upload APK
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: "build-${{ env.short_sha }}"
|
||||
name: "Build ${{ env.short_sha }}"
|
||||
files: app/build/outputs/apk/debug/esp32-aldl-dashboard-${{ env.short_sha }}.apk
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: artifacts/esp32-aldl-dashboard-${{ github.ref_name }}.apk
|
||||
generate_release_notes: true
|
||||
@@ -0,0 +1,143 @@
|
||||
# Privacy Policy for ESP32 ALDL Dashboard
|
||||
|
||||
**Android Application**
|
||||
**Version**: 0.1.0
|
||||
**Last Updated**: June 14, 2026
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The **ESP32 ALDL Dashboard** is a free, open-source Android application designed for vehicle enthusiasts and tuners. It interfaces with GM OBD1 / ALDL-equipped vehicles (such as the 1986 Pontiac Fiero 1227170 ECM) using a custom ESP32 Bluetooth SPP bridge.
|
||||
|
||||
**Core Privacy Principle**:
|
||||
This app collects **only vehicle engine telemetry data** from your car. All data is processed and stored **locally on your device**. The app makes **no network connections**, sends **no data** to any servers, and contains **no analytics, tracking, or crash reporting**.
|
||||
|
||||
---
|
||||
|
||||
## Data Collected
|
||||
|
||||
### What We Collect
|
||||
The app receives and decodes the following vehicle diagnostic data via Bluetooth from your paired ESP32-ALDL device:
|
||||
|
||||
- Engine speed (RPM)
|
||||
- Vehicle speed (MPH)
|
||||
- Coolant temperature (°C / °F)
|
||||
- Manifold Air Temperature (MAT)
|
||||
- Manifold Absolute Pressure (MAP)
|
||||
- Throttle Position Sensor (TPS) voltage
|
||||
- O2 sensor voltage
|
||||
- Battery voltage
|
||||
- Spark advance
|
||||
- Base Pulse Width (BPW)
|
||||
- Idle Air Control (IAC) position
|
||||
- BLM and Integrator fuel trim values
|
||||
- Status flags (Closed Loop, Rich/Lean, TCC Lockup, A/C Clutch, etc.)
|
||||
- Active diagnostic trouble codes (DTCs)
|
||||
|
||||
**Source**: This data comes exclusively from **your vehicle's Engine Control Module (ECM)** through the ALDL port and the ESP32 Bluetooth bridge you provide and pair.
|
||||
|
||||
### What We Do **NOT** Collect
|
||||
- No personal information (name, email, phone, etc.)
|
||||
- No device identifiers or advertising IDs
|
||||
- No location data (location permissions are used only for legacy Bluetooth scanning and are scoped appropriately)
|
||||
- No user accounts or authentication data
|
||||
- No photos, contacts, files, or other personal content
|
||||
- No analytics or usage statistics
|
||||
- No crash reports sent to third parties
|
||||
|
||||
---
|
||||
|
||||
## How Data Is Collected
|
||||
|
||||
- **Exclusively via Bluetooth Classic SPP** connection to your user-paired ESP32-ALDL device.
|
||||
- The app never initiates internet connections or uses any network APIs.
|
||||
- An optional **simulation mode** generates synthetic local data for testing without requiring hardware.
|
||||
|
||||
---
|
||||
|
||||
## Data Storage and Usage
|
||||
|
||||
All data remains **under your full control** on your device:
|
||||
|
||||
| Storage Type | Purpose | Location | User Control |
|
||||
|---------------------------|--------------------------------------|-----------------------------------|-------------------------------|
|
||||
| Room Database (SQLite) | Session history and telemetry points | Private app storage | Deleted on uninstall |
|
||||
| DataStore Preferences | Settings (units, thresholds, toggles)| Private app storage | Managed in Settings screen |
|
||||
| CSV Log Files | TunerPro RT compatible logs | `Downloads/ALDLLogs/` (MediaStore)| User can delete/share |
|
||||
| Raw Binary (.bin) Files | Raw ALDL stream captures | `Downloads/ALDLLogs/` (MediaStore)| Optional, user-controlled |
|
||||
|
||||
- Logging is **optional** and toggleable (auto-log on connect, raw binary recording).
|
||||
- You can browse, open, or share your own log files directly inside the app using Android's share sheet.
|
||||
- No data is ever uploaded, synced to the cloud, or transmitted anywhere.
|
||||
|
||||
---
|
||||
|
||||
## Data Sharing and Disclosure
|
||||
|
||||
**The app does not share any data with anyone.**
|
||||
|
||||
- We (the developer) never receive any data from the app.
|
||||
- No third parties (Google, advertisers, analytics providers, etc.) receive any data.
|
||||
- You may choose to manually export or share your local log files — this is entirely under your control.
|
||||
|
||||
---
|
||||
|
||||
## Permissions Explained
|
||||
|
||||
The app requests only the minimum permissions required for its functionality:
|
||||
|
||||
| Permission | Purpose | Notes |
|
||||
|-----------------------------------------|----------------------------------------------|-------|
|
||||
| `BLUETOOTH`, `BLUETOOTH_ADMIN` | Connect to ESP32 device (Android ≤ 11) | Legacy, maxSdkVersion=30 |
|
||||
| `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT` | Bluetooth scanning and connection (Android 12+) | `neverForLocation` flag used |
|
||||
| `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION` | Required by Android for BT scanning on older versions | Not used for actual location tracking; scoped to maxSdkVersion=30 |
|
||||
| `FOREGROUND_SERVICE` + `FOREGROUND_SERVICE_CONNECTED_DEVICE` | Keep Bluetooth connection alive in background | Shows persistent notification you can tap to disconnect |
|
||||
| `POST_NOTIFICATIONS` | Show connection status notification | Required for foreground service on Android 13+ |
|
||||
|
||||
No other permissions are requested or used.
|
||||
|
||||
---
|
||||
|
||||
## Firebase / Crash Reporting Note
|
||||
|
||||
Earlier documentation mentioned potential Firebase Crashlytics integration. **This is not present in the current version of the app.** There are no Firebase dependencies, no internet permission, and no crash reporting or analytics of any kind.
|
||||
|
||||
---
|
||||
|
||||
## Your Rights and Control
|
||||
|
||||
- Full transparency — the complete source code is available at:
|
||||
**https://git.i3omb.com/gronod/esp32-aldl-android**
|
||||
- You control all logging via the Settings screen.
|
||||
- You can delete individual log files or clear all data by uninstalling the app.
|
||||
- No accounts means no data to request deletion of from a server.
|
||||
|
||||
---
|
||||
|
||||
## Changes to This Policy
|
||||
|
||||
We will update this policy if the app's data handling changes. The latest version will always be available in the app (Settings → About) and in the project repository.
|
||||
|
||||
Because the app has no servers or user accounts, we cannot notify you directly of changes.
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
For questions about this privacy policy or the application, please open an issue in the repository:
|
||||
https://git.i3omb.com/gronod/esp32-aldl-android
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**ESP32 ALDL Dashboard** is a privacy-first, local-only tool.
|
||||
It helps you monitor and log your vehicle's engine data via Bluetooth — and nothing leaves your device unless *you* choose to export it.
|
||||
|
||||
**No tracking. No cloud. No surprises.**
|
||||
|
||||
---
|
||||
|
||||
*This privacy policy is provided in good faith based on a thorough review of the application's source code.*
|
||||
*License: MIT*
|
||||
@@ -39,7 +39,7 @@ A modern, high-performance Android application designed to interface with GM OBD
|
||||
* **TunerPro ADS Translation:** Uses an advanced [ALDLParser](file:///home/gordon/esp32-aldl-android/app/src/test/java/com/example/esp32aldldashboard/ALDLParserTest.kt) mapping raw 25-byte payloads into physical metrics using exact scale conversions, offsets, and non-linear lookup interpolations (derived from the `24-INT10.ads` definition file).
|
||||
* **Foreground Service Operations (New):**
|
||||
* Runs a persistent `BluetoothForegroundService` to keep the Bluetooth socket open and stream telemetry continuously in the background, even when the phone screen is locked or the app is minimized.
|
||||
* **Firebase Integration:** Incorporates Firebase Crashlytics to monitor application stability and track runtime exceptions.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -6,19 +6,32 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.esp32aldldashboard"
|
||||
namespace = "com.gronod.esp32aldldashboard"
|
||||
compileSdk = 36
|
||||
defaultConfig {
|
||||
applicationId = "com.example.esp32aldldashboard"
|
||||
applicationId = "com.gronod.esp32aldldashboard"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
versionName = "0.1.0"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val storeFilePath = System.getenv("SIGNING_STORE_FILE")
|
||||
if (storeFilePath != null) {
|
||||
storeFile = file(storeFilePath)
|
||||
storePassword = System.getenv("SIGNING_STORE_PASSWORD")
|
||||
keyAlias = System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword = System.getenv("SIGNING_KEY_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# ProGuard rules for ESP32 ALDL Dashboard
|
||||
# Compose + Room + Kotlin Serialization + Navigation3
|
||||
|
||||
# --- Crash stack traces: preserve file names and line numbers ---
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
# --- General reflection / generic type preservation ---
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Signature
|
||||
-keepattributes InnerClasses
|
||||
-keepattributes EnclosingMethod
|
||||
|
||||
# --- Room ---
|
||||
# RoomDatabase subclass is instantiated by reflection
|
||||
-keep class * extends androidx.room.RoomDatabase { *; }
|
||||
# Keep all @Entity, @Dao, @Database annotated members
|
||||
-keep @androidx.room.Entity class * { *; }
|
||||
-keep @androidx.room.Dao interface * { *; }
|
||||
-keep @androidx.room.Database class * { *; }
|
||||
|
||||
# --- Kotlin Serialization ---
|
||||
# Only nav key objects annotated with @Serializable need explicit protection;
|
||||
# the Kotlin compiler plugin generates keep rules for everything else.
|
||||
-keep @kotlinx.serialization.Serializable class com.gronod.esp32aldldashboard.** { *; }
|
||||
|
||||
# --- Enums ---
|
||||
# Preserve enum names used by Room, Bluetooth state, and conditional logic
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
# --- Application, Service, ViewModel, and Factory ---
|
||||
# These are instantiated by the Android framework or ViewModelProvider via reflection
|
||||
-keep class com.gronod.esp32aldldashboard.AldlApplication { *; }
|
||||
-keep class com.gronod.esp32aldldashboard.bluetooth.BluetoothForegroundService { *; }
|
||||
-keep class * extends androidx.lifecycle.ViewModel { *; }
|
||||
-keep class * extends androidx.lifecycle.ViewModelProvider$Factory { *; }
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.ui.main
|
||||
package com.gronod.esp32aldldashboard.ui.main
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
@@ -7,7 +7,7 @@ import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
/** UI tests for [com.example.esp32aldldashboard.ui.main.MainScreen]. */
|
||||
/** UI tests for [com.gronod.esp32aldldashboard.ui.main.MainScreen]. */
|
||||
class MainScreenTest {
|
||||
|
||||
@get:Rule val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.example.esp32aldldashboard
|
||||
package com.gronod.esp32aldldashboard
|
||||
|
||||
import android.app.Application
|
||||
import com.example.esp32aldldashboard.bluetooth.BluetoothService
|
||||
import com.example.esp32aldldashboard.repository.BLMTableRepository
|
||||
import com.example.esp32aldldashboard.repository.ChartPreferencesRepository
|
||||
import com.example.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.example.esp32aldldashboard.repository.TelemetryRepository
|
||||
import com.example.esp32aldldashboard.logging.CsvLogger
|
||||
import com.example.esp32aldldashboard.logging.RawStreamLogger
|
||||
import com.example.esp32aldldashboard.data.database.TelemetryDatabase
|
||||
import com.gronod.esp32aldldashboard.bluetooth.BluetoothService
|
||||
import com.gronod.esp32aldldashboard.repository.BLMTableRepository
|
||||
import com.gronod.esp32aldldashboard.repository.ChartPreferencesRepository
|
||||
import com.gronod.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.gronod.esp32aldldashboard.repository.TelemetryRepository
|
||||
import com.gronod.esp32aldldashboard.logging.CsvLogger
|
||||
import com.gronod.esp32aldldashboard.logging.RawStreamLogger
|
||||
import com.gronod.esp32aldldashboard.data.database.TelemetryDatabase
|
||||
|
||||
class AldlApplication : Application() {
|
||||
lateinit var bluetoothService: BluetoothService
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard
|
||||
package com.gronod.esp32aldldashboard
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.example.esp32aldldashboard.theme.ESP32ALDLDashboardTheme
|
||||
import com.gronod.esp32aldldashboard.theme.ESP32ALDLDashboardTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard
|
||||
package com.gronod.esp32aldldashboard
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
@@ -8,7 +8,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import com.example.esp32aldldashboard.ui.main.MainScreen
|
||||
import com.gronod.esp32aldldashboard.ui.main.MainScreen
|
||||
|
||||
@Composable
|
||||
fun MainNavigation() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard
|
||||
package com.gronod.esp32aldldashboard
|
||||
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.bluetooth
|
||||
package com.gronod.esp32aldldashboard.bluetooth
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
@@ -9,9 +9,9 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.example.esp32aldldashboard.AldlApplication
|
||||
import com.example.esp32aldldashboard.MainActivity
|
||||
import com.example.esp32aldldashboard.R
|
||||
import com.gronod.esp32aldldashboard.AldlApplication
|
||||
import com.gronod.esp32aldldashboard.MainActivity
|
||||
import com.gronod.esp32aldldashboard.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.bluetooth
|
||||
package com.gronod.esp32aldldashboard.bluetooth
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
@@ -7,10 +7,10 @@ import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothSocket
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.example.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.example.esp32aldldashboard.parser.ALDLParser
|
||||
import com.example.esp32aldldashboard.logging.RawStreamLogger
|
||||
import com.example.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLParser
|
||||
import com.gronod.esp32aldldashboard.logging.RawStreamLogger
|
||||
import com.gronod.esp32aldldashboard.repository.SettingsRepository
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -234,7 +234,7 @@ class BluetoothService(
|
||||
payload[24] = bpwLowRaw.toByte()
|
||||
|
||||
val parsed = ALDLParser.parseFrame(payload)
|
||||
if (parsed is com.example.esp32aldldashboard.parser.ALDLParseResult.Success) {
|
||||
if (parsed is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success) {
|
||||
_latestFrame.value = parsed.frame
|
||||
val hexString = payload.joinToString(" ") { String.format("%02X", it) }
|
||||
addRawHexLog("AA 55 $hexString (SIMULATED)")
|
||||
@@ -380,7 +380,7 @@ class BluetoothService(
|
||||
|
||||
val parsed = ALDLParser.parseFrame(payload)
|
||||
when (parsed) {
|
||||
is com.example.esp32aldldashboard.parser.ALDLParseResult.Success -> {
|
||||
is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success -> {
|
||||
_framesReceived.value += 1
|
||||
framesInCurrentSecond++
|
||||
|
||||
@@ -397,11 +397,11 @@ class BluetoothService(
|
||||
addRawHexLog("AA 55 $hexString")
|
||||
}
|
||||
}
|
||||
is com.example.esp32aldldashboard.parser.ALDLParseResult.InvalidData -> {
|
||||
is com.gronod.esp32aldldashboard.parser.ALDLParseResult.InvalidData -> {
|
||||
_parseErrors.value += 1
|
||||
Log.w(TAG, "Invalid frame: ${parsed.reason}")
|
||||
}
|
||||
com.example.esp32aldldashboard.parser.ALDLParseResult.Incomplete -> {
|
||||
com.gronod.esp32aldldashboard.parser.ALDLParseResult.Incomplete -> {
|
||||
// Handled by size check
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.data
|
||||
package com.gronod.esp32aldldashboard.data
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.data.database
|
||||
package com.gronod.esp32aldldashboard.data.database
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.data.database
|
||||
package com.gronod.esp32aldldashboard.data.database
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.data.database
|
||||
package com.gronod.esp32aldldashboard.data.database
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.data.database
|
||||
package com.gronod.esp32aldldashboard.data.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.example.esp32aldldashboard.logging
|
||||
package com.gronod.esp32aldldashboard.logging
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import com.example.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLFrame
|
||||
import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.logging
|
||||
package com.gronod.esp32aldldashboard.logging
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.parser
|
||||
package com.gronod.esp32aldldashboard.parser
|
||||
|
||||
data class ALDLFrame(
|
||||
val rawBytes: ByteArray,
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.parser
|
||||
package com.gronod.esp32aldldashboard.parser
|
||||
|
||||
// Approximate Engine Load (0.0 to 1.0)
|
||||
// Very rough approximation: MAP (kPa) / ~100 kPa (atmospheric at sea level)
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.parser
|
||||
package com.gronod.esp32aldldashboard.parser
|
||||
|
||||
object TroubleCodeDictionary {
|
||||
val DTC_DESCRIPTIONS = mapOf(
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.repository
|
||||
package com.gronod.esp32aldldashboard.repository
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.example.esp32aldldashboard.repository
|
||||
package com.gronod.esp32aldldashboard.repository
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.*
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.example.esp32aldldashboard.ui.charts.ChartParameter
|
||||
import com.gronod.esp32aldldashboard.ui.charts.ChartParameter
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.repository
|
||||
package com.gronod.esp32aldldashboard.repository
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
@@ -1,15 +1,15 @@
|
||||
package com.example.esp32aldldashboard.repository
|
||||
package com.gronod.esp32aldldashboard.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.example.esp32aldldashboard.bluetooth.BluetoothService
|
||||
import com.example.esp32aldldashboard.bluetooth.BluetoothForegroundService
|
||||
import com.example.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.example.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.example.esp32aldldashboard.data.database.SessionEntity
|
||||
import com.example.esp32aldldashboard.data.database.TelemetryDao
|
||||
import com.example.esp32aldldashboard.data.database.TelemetryDataPointEntity
|
||||
import com.example.esp32aldldashboard.logging.CsvLogger
|
||||
import com.gronod.esp32aldldashboard.bluetooth.BluetoothService
|
||||
import com.gronod.esp32aldldashboard.bluetooth.BluetoothForegroundService
|
||||
import com.gronod.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.gronod.esp32aldldashboard.data.database.SessionEntity
|
||||
import com.gronod.esp32aldldashboard.data.database.TelemetryDao
|
||||
import com.gronod.esp32aldldashboard.data.database.TelemetryDataPointEntity
|
||||
import com.gronod.esp32aldldashboard.logging.CsvLogger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.theme
|
||||
package com.gronod.esp32aldldashboard.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.theme
|
||||
package com.gronod.esp32aldldashboard.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.theme
|
||||
package com.gronod.esp32aldldashboard.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.ui.blm
|
||||
package com.gronod.esp32aldldashboard.ui.blm
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
@@ -18,7 +18,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.example.esp32aldldashboard.repository.BLMTableRepository
|
||||
import com.gronod.esp32aldldashboard.repository.BLMTableRepository
|
||||
|
||||
@Composable
|
||||
fun BLMTableScreen(
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.example.esp32aldldashboard.ui.blm
|
||||
package com.gronod.esp32aldldashboard.ui.blm
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.example.esp32aldldashboard.repository.BLMCellData
|
||||
import com.example.esp32aldldashboard.repository.BLMTableRepository
|
||||
import com.gronod.esp32aldldashboard.repository.BLMCellData
|
||||
import com.gronod.esp32aldldashboard.repository.BLMTableRepository
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class BLMTableViewModel(private val repository: BLMTableRepository) : ViewModel() {
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.example.esp32aldldashboard.ui.charts
|
||||
package com.gronod.esp32aldldashboard.ui.charts
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.example.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLFrame
|
||||
|
||||
enum class ChartParameter(
|
||||
val displayName: String,
|
||||
@@ -1,5 +1,5 @@
|
||||
@file:OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
package com.example.esp32aldldashboard.ui.charts
|
||||
package com.gronod.esp32aldldashboard.ui.charts
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.foundation.Canvas
|
||||
@@ -16,8 +16,8 @@ import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.example.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.example.esp32aldldashboard.repository.ChartPreferencesRepository
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.gronod.esp32aldldashboard.repository.ChartPreferencesRepository
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.ui.components
|
||||
package com.gronod.esp32aldldashboard.ui.components
|
||||
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.ui.main
|
||||
package com.gronod.esp32aldldashboard.ui.main
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
@@ -16,11 +16,11 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.example.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.example.esp32aldldashboard.parser.TroubleCodeDictionary
|
||||
import com.example.esp32aldldashboard.ui.components.RpmGauge
|
||||
import com.example.esp32aldldashboard.ui.components.TpsBar
|
||||
import com.gronod.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.gronod.esp32aldldashboard.parser.TroubleCodeDictionary
|
||||
import com.gronod.esp32aldldashboard.ui.components.RpmGauge
|
||||
import com.gronod.esp32aldldashboard.ui.components.TpsBar
|
||||
|
||||
// Theme Colors
|
||||
val DarkBg = Color(0xFF0F0F12)
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.ui.main
|
||||
package com.gronod.esp32aldldashboard.ui.main
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
@@ -18,11 +18,11 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.example.esp32aldldashboard.AldlApplication
|
||||
import com.example.esp32aldldashboard.ui.blm.BLMTableScreen
|
||||
import com.example.esp32aldldashboard.ui.blm.BLMTableViewModelFactory
|
||||
import com.example.esp32aldldashboard.ui.charts.ChartsScreen
|
||||
import com.example.esp32aldldashboard.ui.settings.SettingsScreen
|
||||
import com.gronod.esp32aldldashboard.AldlApplication
|
||||
import com.gronod.esp32aldldashboard.ui.blm.BLMTableScreen
|
||||
import com.gronod.esp32aldldashboard.ui.blm.BLMTableViewModelFactory
|
||||
import com.gronod.esp32aldldashboard.ui.charts.ChartsScreen
|
||||
import com.gronod.esp32aldldashboard.ui.settings.SettingsScreen
|
||||
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
@@ -131,7 +131,7 @@ fun MainScreen(
|
||||
modifier = modifier.padding(paddingValues)
|
||||
)
|
||||
2 -> {
|
||||
val blmViewModel: com.example.esp32aldldashboard.ui.blm.BLMTableViewModel = viewModel(
|
||||
val blmViewModel: com.gronod.esp32aldldashboard.ui.blm.BLMTableViewModel = viewModel(
|
||||
factory = BLMTableViewModelFactory(app.blmTableRepository)
|
||||
)
|
||||
BLMTableScreen(
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.example.esp32aldldashboard.ui.main
|
||||
package com.gronod.esp32aldldashboard.ui.main
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.example.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.example.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.example.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.example.esp32aldldashboard.repository.TelemetryRepository
|
||||
import com.gronod.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLFrame
|
||||
import com.gronod.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.gronod.esp32aldldashboard.repository.TelemetryRepository
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.example.esp32aldldashboard.ui.main
|
||||
package com.gronod.esp32aldldashboard.ui.main
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.example.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.example.esp32aldldashboard.repository.TelemetryRepository
|
||||
import com.gronod.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.gronod.esp32aldldashboard.repository.TelemetryRepository
|
||||
|
||||
class MainScreenViewModelFactory(
|
||||
private val telemetryRepository: TelemetryRepository,
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.ui.settings
|
||||
package com.gronod.esp32aldldashboard.ui.settings
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.*
|
||||
@@ -14,8 +14,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import com.example.esp32aldldashboard.repository.FileType
|
||||
import com.example.esp32aldldashboard.repository.LoggedFile
|
||||
import com.gronod.esp32aldldashboard.repository.FileType
|
||||
import com.gronod.esp32aldldashboard.repository.LoggedFile
|
||||
|
||||
@Composable
|
||||
fun LogFilesDialog(
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.ui.settings
|
||||
package com.gronod.esp32aldldashboard.ui.settings
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -9,8 +9,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.example.esp32aldldashboard.repository.LoggedFile
|
||||
import com.example.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.gronod.esp32aldldashboard.repository.LoggedFile
|
||||
import com.gronod.esp32aldldashboard.repository.SettingsRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.example.esp32aldldashboard
|
||||
package com.gronod.esp32aldldashboard
|
||||
|
||||
import com.example.esp32aldldashboard.parser.ALDLParser
|
||||
import com.gronod.esp32aldldashboard.parser.ALDLParser
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
@@ -20,8 +20,8 @@ class ALDLParserTest {
|
||||
)
|
||||
|
||||
val result = ALDLParser.parseFrame(rawPayload)
|
||||
assertTrue(result is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
val frame = (result as com.example.esp32aldldashboard.parser.ALDLParseResult.Success).frame
|
||||
assertTrue(result is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
val frame = (result as com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success).frame
|
||||
|
||||
// Assert values based on 24-INT10.ads specifications:
|
||||
assertEquals(95, frame.iacPosition) // u[3] (Byte 4)
|
||||
@@ -78,7 +78,7 @@ class ALDLParserTest {
|
||||
this[7] = 255.toByte() // 255 * 25 = 6375 RPM
|
||||
}
|
||||
val validResult = ALDLParser.parseFrame(validPayload)
|
||||
assertTrue("Max representable RPM should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
assertTrue("Max representable RPM should be valid", validResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,28 +88,28 @@ class ALDLParserTest {
|
||||
this[17] = 80.toByte()
|
||||
}
|
||||
val minResult = ALDLParser.parseFrame(minPayload)
|
||||
assertTrue("Battery at 8V should be valid", minResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
assertTrue("Battery at 8V should be valid", minResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
|
||||
// Max valid: 18V (180 * 0.1 = 18.0V)
|
||||
val maxPayload = createBasePayload().apply {
|
||||
this[17] = 180.toByte()
|
||||
}
|
||||
val maxResult = ALDLParser.parseFrame(maxPayload)
|
||||
assertTrue("Battery at 18V should be valid", maxResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
assertTrue("Battery at 18V should be valid", maxResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
|
||||
// Too low: 4.9V (49 * 0.1 = 4.9V, below 5V minimum)
|
||||
val lowPayload = createBasePayload().apply {
|
||||
this[17] = 49.toByte()
|
||||
}
|
||||
val lowResult = ALDLParser.parseFrame(lowPayload)
|
||||
assertTrue("Battery below 5V should be rejected", lowResult is com.example.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
|
||||
assertTrue("Battery below 5V should be rejected", lowResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
|
||||
|
||||
// Too high: 20.1V (201 * 0.1 = 20.1V, above 20V maximum)
|
||||
val highPayload = createBasePayload().apply {
|
||||
this[17] = 201.toByte()
|
||||
}
|
||||
val highResult = ALDLParser.parseFrame(highPayload)
|
||||
assertTrue("Battery above 20V should be rejected", highResult is com.example.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
|
||||
assertTrue("Battery above 20V should be rejected", highResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -119,7 +119,7 @@ class ALDLParserTest {
|
||||
this[8] = 255.toByte()
|
||||
}
|
||||
val validResult = ALDLParser.parseFrame(validPayload)
|
||||
assertTrue("Max representable TPS should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
assertTrue("Max representable TPS should be valid", validResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,21 +127,21 @@ class ALDLParserTest {
|
||||
// Valid temperature: 89 * 0.75 - 40 = 26.75C (within -45 to 220 range)
|
||||
val validPayload = createBasePayload()
|
||||
val validResult = ALDLParser.parseFrame(validPayload)
|
||||
assertTrue("Valid coolant temp should be accepted", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
assertTrue("Valid coolant temp should be accepted", validResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
|
||||
// Max representable: 255 * 0.75 - 40 = 151.25C
|
||||
val maxPayload = createBasePayload().apply {
|
||||
this[4] = 255.toByte()
|
||||
}
|
||||
val maxResult = ALDLParser.parseFrame(maxPayload)
|
||||
assertTrue("Max representable coolant temp should be accepted", maxResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
assertTrue("Max representable coolant temp should be accepted", maxResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
|
||||
// Min representable: 0 * 0.75 - 40 = -40C
|
||||
val minPayload = createBasePayload().apply {
|
||||
this[4] = 0.toByte()
|
||||
}
|
||||
val minResult = ALDLParser.parseFrame(minPayload)
|
||||
assertTrue("Min representable coolant temp should be accepted", minResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
assertTrue("Min representable coolant temp should be accepted", minResult is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Success)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -149,7 +149,7 @@ class ALDLParserTest {
|
||||
// Payload with only 24 bytes (incomplete)
|
||||
val incompletePayload = ByteArray(24) { 0x20.toByte() }
|
||||
val result = ALDLParser.parseFrame(incompletePayload)
|
||||
assertTrue("Incomplete payload should return Incomplete result", result is com.example.esp32aldldashboard.parser.ALDLParseResult.Incomplete)
|
||||
assertTrue("Incomplete payload should return Incomplete result", result is com.gronod.esp32aldldashboard.parser.ALDLParseResult.Incomplete)
|
||||
}
|
||||
|
||||
private fun createBasePayload(): ByteArray {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.esp32aldldashboard.parser
|
||||
package com.gronod.esp32aldldashboard.parser
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
@@ -1,9 +1,9 @@
|
||||
@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
package com.example.esp32aldldashboard.ui.main
|
||||
package com.gronod.esp32aldldashboard.ui.main
|
||||
|
||||
import com.example.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.example.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.example.esp32aldldashboard.repository.TelemetryRepository
|
||||
import com.gronod.esp32aldldashboard.bluetooth.ConnectionState
|
||||
import com.gronod.esp32aldldashboard.repository.SettingsRepository
|
||||
import com.gronod.esp32aldldashboard.repository.TelemetryRepository
|
||||
import io.mockk.*
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
@@ -0,0 +1,14 @@
|
||||
ESP32 ALDL Dashboard connects your Android phone to classic GM OBD1 vehicles (such as the 1986 Pontiac Fiero 1227170 ECM) via a custom ESP32 Bluetooth SPP bridge, decoding the 160-baud ALDL datastream in real time.
|
||||
|
||||
Features:
|
||||
- Animated radial RPM gauge and live readouts for speed, coolant temp, MAP, TPS, O2 sensor, MAT, and battery voltage
|
||||
- Status flags for closed-loop mode, rich/lean mixture, TCC lockup, and A/C clutch
|
||||
- BLM and Integrator fuel-trim heatmap grid across 14 RPM and 17 MAP bands with dynamic colour coding
|
||||
- Multi-parameter line charts in single or 2x2 grid mode for 12 selectable metrics
|
||||
- Trouble code decoder with human-readable descriptions
|
||||
- Dual logging: Room database sessions and TunerPro RT-compatible CSV export to Downloads/ALDLLogs
|
||||
- Optional raw binary stream recording for advanced playback and diagnostics
|
||||
- In-app log browser for viewing, opening, or sharing CSV and BIN files
|
||||
- Persistent settings for temperature units, alert thresholds, and auto-logging preferences
|
||||
|
||||
Requirements: Android 7.0 or higher; an ESP32 device programmed to bridge the ALDL serial pin to Classic Bluetooth SPP (named ESP32-ALDL).
|
||||
|
After Width: | Height: | Size: 287 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 176 KiB |
@@ -0,0 +1 @@
|
||||
Real-time GM OBD1 ALDL dashboard via ESP32 Bluetooth for classic cars.
|
||||
@@ -0,0 +1 @@
|
||||
ESP32 ALDL Dashboard
|
||||
@@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
|
||||