Created
December 14, 2025 20:05
-
-
Save Chukslord1/16069c6de8822158ff291fd38baa25e3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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