Skip to content

Instantly share code, notes, and snippets.

@Chukslord1
Created December 14, 2025 20:05
Show Gist options
  • Select an option

  • Save Chukslord1/16069c6de8822158ff291fd38baa25e3 to your computer and use it in GitHub Desktop.

Select an option

Save Chukslord1/16069c6de8822158ff291fd38baa25e3 to your computer and use it in GitHub Desktop.
dependencies:
expressions: ^0.2.5
import 'package:expressions/expressions.dart';
/// ------------------------------------------------------------
/// FUNCTION 1: Find the richest PID response
/// ------------------------------------------------------------
List<String> findBestPidResponse(List<String> bytes, String targetPid) {
print("pid responses received: $bytes for target pid $targetPid");
List<String> bestData = ["00", "00", "00", "00"];
int maxBitsSet = -1;
for (int i = 0; i <= bytes.length - 6; i++) {
if (bytes[i] == "41" && bytes[i + 1] == targetPid) {
final candidateData = bytes.sublist(i + 2, i + 6);
if (candidateData.length < 4) continue;
final binaryStr = candidateData
.map((h) => int.parse(h, radix: 16)
.toRadixString(2)
.padLeft(8, '0'))
.join();
final bitCount =
binaryStr.split('').where((c) => c == '1').length;
if (bitCount > maxBitsSet) {
maxBitsSet = bitCount;
bestData = candidateData;
}
}
}
return bestData;
}
/// ------------------------------------------------------------
/// FUNCTION 2: Sanitize raw ELM response
/// ------------------------------------------------------------
List<String> sanitizeObdResponse(String? obdResponse) {
if (obdResponse == null || obdResponse.isEmpty) return [];
obdResponse = obdResponse.replaceAll("undefined", "");
obdResponse = obdResponse
.replaceAll(RegExp(r'SEARCHING\.+'), '')
.replaceAll('STOPPED', '')
.replaceAll('NO DATA', '')
.replaceAll('UNABLE TO CONNECT', '');
final cleanHex = obdResponse
.replaceAll(RegExp(r'[^0-9A-Fa-f]'), '')
.toUpperCase();
return RegExp(r'.{1,2}')
.allMatches(cleanHex)
.map((m) => m.group(0)!)
.toList();
}
/// ------------------------------------------------------------
/// MAIN FUNCTION: Recursive supported PID scan
/// ------------------------------------------------------------
Future<List<String>> getSupportedPidsFromVehicle({
String startPid = "00",
List<String>? accumulatedPids,
}) async {
accumulatedPids ??= [];
try {
final command = "01$startPid";
final knownPids = ["03"]; // DTC command
print("Checking supported PIDs starting at $command");
final response = await sendObdCommand(command);
final sanitized = sanitizeObdResponse(response);
final dataBytes = findBestPidResponse(sanitized, startPid);
if (dataBytes.every((b) => b == "00")) {
print("Block $startPid returned no supported PIDs");
return accumulatedPids;
}
final binaryString = dataBytes
.map((h) => int.parse(h, radix: 16)
.toRadixString(2)
.padLeft(8, '0'))
.join();
if (binaryString.length < 32) {
print("Invalid PID response length");
return accumulatedPids;
}
final offset = int.parse(startPid, radix: 16);
for (int i = 0; i < 32; i++) {
if (binaryString[i] == '1') {
final pidNumber = offset + i + 1;
final pidHex =
pidNumber.toRadixString(16).toUpperCase().padLeft(2, '0');
accumulatedPids.add("01$pidHex");
}
}
final hasNextBlock = binaryString[31] == '1';
if (hasNextBlock) {
final nextPid =
(offset + 32).toRadixString(16).toUpperCase().padLeft(2, '0');
return await getSupportedPidsFromVehicle(
startPid: nextPid,
accumulatedPids: accumulatedPids,
);
}
return [...accumulatedPids, ...knownPids];
} catch (e) {
print("PID scan error: $e");
return accumulatedPids;
}
}
/// ------------------------------------------------------------
/// Run a single PID and apply its formula
/// ------------------------------------------------------------
Future<dynamic> runAPid(
String pid,
String formula,
String property,
) async {
try {
print("now running pid: $pid");
final response = await sendObdCommand(pid);
print(response);
final cleaned = sanitizeObdResponse(response);
if (cleaned.isEmpty) return null;
if (formula.contains("bit-mapped") || formula.contains("lookup")) {
return parseDtcResponse(cleaned.join(' '));
}
final A = cleaned.length > 2 ? int.parse(cleaned[2], radix: 16) : 0;
final B = cleaned.length > 3 ? int.parse(cleaned[3], radix: 16) : 0;
return applyCorrectFormula(formula, A, B);
} catch (e) {
print(e);
return null;
}
}
/// ------------------------------------------------------------
/// Apply correct formula
/// ------------------------------------------------------------
num applyCorrectFormula(String formula, int A, [int B = 0]) {
switch (formula) {
case "A":
case "a":
return A;
case "B":
case "b":
return B;
default:
return evaluateFormula(formula, A, B);
}
}
/// ------------------------------------------------------------
/// Safe formula evaluator (JS evaluate equivalent)
/// ------------------------------------------------------------
num evaluateFormula(String formula, int A, int B) {
final expression = Expression.parse(formula);
final evaluator = const ExpressionEvaluator();
return evaluator.eval(
expression,
{
'A': A,
'B': B,
},
) as num;
}
/// ------------------------------------------------------------
/// Parse DTC (bit-mapped / lookup responses)
/// ------------------------------------------------------------
List<String> parseDtcResponse(String hexString) {
try {
final bytes = hexString.trim().split(RegExp(r'\s+'));
if (bytes.length < 3) return [];
final dataBytes = bytes.sublist(1);
final dtcs = <String>[];
for (int i = 0; i < dataBytes.length - 1; i += 2) {
final b1 = int.tryParse(dataBytes[i], radix: 16);
final b2 = int.tryParse(dataBytes[i + 1], radix: 16);
if (b1 == null || b2 == null) continue;
if (b1 == 0 && b2 == 0) break;
final firstChar = ["P", "C", "B", "U"][(b1 & 0xC0) >> 6];
final code =
firstChar +
((b1 & 0x30) >> 4).toString() +
(b1 & 0x0F).toString() +
b2.toRadixString(16).padLeft(2, '0').toUpperCase();
dtcs.add(code);
}
return dtcs;
} catch (_) {
return [];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment