Compare commits

...

43 Commits

Author SHA1 Message Date
gronod c9e9fede29 Merge branch 'main' of https://git.i3omb.com/gronod/esp32-aldl-android
Build and Release APK / build-and-release (push) Successful in 6m19s
2026-06-14 13:57:00 +01:00
gronod 180ed20741 Release v0.1.0 2026-06-14 13:56:48 +01:00
gronod b21efd5c99 Update app/build.gradle.kts 2026-06-14 13:55:37 +01:00
gronod 8bf3d53448 Refine ProGuard rules: add crash reporting attributes, tighten Room/serialization keeps, preserve enums and framework classes
Build and Release APK / build-and-release (push) Has been cancelled
2026-06-14 13:51:52 +01:00
gronod 3278fd54b2 Update app/build.gradle.kts
Build and Release APK / build-and-release (push) Failing after 4m43s
Set isMinifyEnabled=true
2026-06-14 13:43:59 +01:00
gronod d034fb1b98 Add Gradle distribution SHA256 checksum and set up fastlane metadata 2026-06-14 13:36:50 +01:00
gronod 807ac93646 Upload files to "/" 2026-06-14 13:34:10 +01:00
gronod a9dec01688 Upload files to "screenshots" 2026-06-14 13:20:02 +01:00
gronod 0415ec366a Update app/proguard-rules.pro (#13)
Build and Release APK / build-and-release (push) Successful in 5m25s
Reviewed-on: #13
2026-06-14 12:19:37 +01:00
gronod c229dc4e64 Update app/proguard-rules.pro 2026-06-14 12:19:17 +01:00
gronod 4b48d455e4 Upload files to "app" 2026-06-14 12:19:07 +01:00
gronod b646d8eaa8 revert 4d0ab48c1f
revert chore: bump version to 1.0.0 (versionCode 2) and enable minification for release builds
2026-06-14 12:17:26 +01:00
gronod 63aaffc1d7 Delete app/proguard-rules.pro 2026-06-14 12:15:52 +01:00
gronod da8f216edd Update app/proguard-rules.pro 2026-06-14 12:14:58 +01:00
gronod 4855712da5 Add app/proguard-rules.pro 2026-06-14 12:06:08 +01:00
gronod 0387de99c7 Delete app/proguard-rules.pro 2026-06-14 12:05:50 +01:00
gronod 4d0ab48c1f chore: bump version to 1.0.0 (versionCode 2) and enable minification for release builds
Build and Release APK / build-and-release (push) Failing after 5m15s
2026-06-14 11:53:26 +01:00
gronod d40a63ec4a ci: add release signing configuration and tag validation to workflows
Build and Release APK / build-and-release (push) Successful in 5m16s
2026-06-14 11:28:14 +01:00
gronod 2e94463f37 refactor: update package namespace from com.example to com.gronod 2026-06-14 11:15:01 +01:00
gronod 6a6232649c Update PRIVACY.md 2026-06-14 11:08:00 +01:00
gronod ccfaf2686c Update _PRIVACY.md 2026-06-14 11:07:42 +01:00
gronod 621f67aa38 Upload files to "PRIVACY.md" 2026-06-14 11:05:51 +01:00
gronod df2f851a0d Update README.md
Removed window reference to firebase analytics (not implemented)
2026-06-14 10:55:04 +01:00
gronod 0b43a024f8 Add .gitea/workflows/release.yml (#11)
Build and Release APK / build-and-release (push) Successful in 2m52s
Reviewed-on: #11
2026-06-14 10:19:32 +01:00
gronod 877db7f098 Merge pull request 'feat: implement telemetry persistence, data visualization, and improved bluetooth stream parsing with frame statistics' (#10) from v2 into main
Build and Release APK / build-and-release (push) Successful in 10m32s
Reviewed-on: #10
2026-06-14 08:39:15 +01:00
gronod e5c4e00d76 ci: add Android SDK setup to release workflow and update deployment target selection timestamp
Build and Release APK / build-and-release (push) Successful in 9m10s
2026-06-12 15:50:58 +01:00
gronod d4c87efe7e ci: add GitHub Actions workflow to automate APK builds and releases
Build and Release APK / build-and-release (push) Failing after 10m6s
2026-06-12 15:33:52 +01:00
gronod 4a5379e3a1 feat: enable horizontal scrolling for the BLM table container 2026-06-12 15:27:58 +01:00
gronod 5bd096f54b test: fix ViewModel state collection in tests and update ALDL parser boundary validations to match byte limits 2026-06-12 12:31:39 +01:00
gronod 7c2505ab7b test: refactor MainScreenViewModel tests to use repository mocks instead of fake context 2026-06-12 12:29:12 +01:00
gronod d8e656284a feat: add MockK dependency and update README documentation for diagnostic features 2026-06-12 12:29:02 +01:00
gronod b3c475ad18 feat: add BLMTableViewModel with factory and extended Material icons dependency 2026-06-12 12:15:44 +01:00
gronod b06de4e2fe chore: add Windsurf workflows directory to gitignore 2026-06-12 11:14:21 +01:00
gronod 9838378933 feat: add logged files viewer in settings 2026-06-12 10:58:25 +01:00
gronod bb47d61eec feat: add BLM/INT heatmap table with RPM/MAP bands 2026-06-12 10:56:51 +01:00
gronod f760a9f300 feat: add multi-parameter chart view with graph icon 2026-06-12 10:52:03 +01:00
gronod 05a5acac10 refactor: move trouble codes to bottom of dashboard 2026-06-12 10:50:23 +01:00
gronod 6c57086f25 chore: add Firebase Crashlytics settings and fix temperature unit toggle state handling 2026-06-12 10:28:36 +01:00
gronod 7653876fed feat: add raw binary stream logging, foreground service support, and improved frame header validation
- Implement RawStreamLogger for binary datastream recording with .bin file output
- Add foreground service (BluetoothForegroundService) to maintain Bluetooth connection during background operation
- Enhance frame header detection with 27-byte lookahead validation to filter false AA 55 sequences in payload data
- Add raw data recording toggle in settings with persistent preference storage
- Refactor Main
2026-06-12 10:18:31 +01:00
gronod 89e2ed4cd0 chore: add MIT License 2026-06-12 09:59:00 +01:00
gronod ae997da849 feat: add 24_INT10_mod.adx configuration and initialize project planning mode 2026-06-12 09:11:09 +01:00
gronod 5383c5bd11 chore: downgrade Kotlin to 2.1.0, add KSP support, and migrate Room to KSP 2026-06-12 00:17:55 +01:00
gronod 29423f6aff feat: implement telemetry persistence, data visualization, and improved bluetooth stream parsing with frame statistics 2026-06-11 23:59:42 +01:00
57 changed files with 440 additions and 140 deletions
+1
View File
@@ -0,0 +1 @@
-NoNewline
+77
View File
@@ -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"
+32 -22
View File
@@ -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
+143
View File
@@ -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*
+1 -1
View File
@@ -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.
---
+17 -4
View File
@@ -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")
}
}
+39
View File
@@ -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).
Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

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
Binary file not shown.
+1
View File
@@ -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