[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{kt,kts}]\nij_kotlin_code_style_defaults = KOTLIN_OFFICIAL\nij_kotlin_allow_trailing_comma = true\nij_kotlin_allow_trailing_comma_on_call_site = true\nktlint_standard_backing-property-naming = disabled\nktlint_standard_filename = disabled\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n    branches:\n      - master\n  push:\n    branches:\n      - master\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-java@v4\n        with:\n          distribution: temurin\n          java-version: \"25\"\n      - uses: gradle/actions/setup-gradle@v4\n      - name: Verify\n        run: ./gradlew --no-daemon clean test ktlintCheck detekt\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by https://www.gitignore.io/api/macos,kotlin,gradle,intellij+iml\n# Edit at https://www.gitignore.io/?templates=macos,kotlin,gradle,intellij+iml\n\n### Intellij+iml ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn.  Uncomment if using\n# auto-import.\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### Intellij+iml Patch ###\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\n\n*.iml\nmodules.xml\n.idea/misc.xml\n*.ipr\n\n### Kotlin ###\n# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.nar\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### Gradle ###\n.gradle\nbuild/\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle-wrapper.jar\n\n# Cache of project\n.gradletasknamecache\n\n# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898\n# gradle/wrapper/gradle-wrapper.properties\n\n### Gradle Patch ###\n**/build/\n\n# End of https://www.gitignore.io/api/macos,kotlin,gradle,intellij+iml\n"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JetCodeStyleSettings>\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </JetCodeStyleSettings>\n    <codeStyleSettings language=\"kotlin\">\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/encodings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\" addBOMForNewFiles=\"with NO BOM\" />\n</project>"
  },
  {
    "path": ".idea/kotlinScripting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"KotlinScriptingSettings\">\n    <option name=\"isAutoReloadEnabled\" value=\"true\" />\n    <option name=\"suppressDefinitionsCheck\" value=\"true\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "LLM_CONTEXT.md",
    "content": "# LLM Context for `kotlin-obd-api`\n\nThis document helps coding assistants correctly choose and apply this library for OBD-II tasks.\n\n## Project Summary\n\n- Name: `kotlin-obd-api`\n- Language: Kotlin (JVM)\n- Package: `com.github.eltonvs:kotlin-obd-api` (JitPack)\n- Primary purpose: query and parse OBD-II / ELM327 commands from Kotlin apps\n- Typical targets: any Kotlin/JVM app that can provide an `InputStream` and an `OutputStream` (Android, desktop, backend services, CLI tools, embedded Linux, etc.)\n\n## When to Use\n\nUse this library when the task involves:\n- Reading OBD-II telemetry (RPM, speed, MAF, temperatures, pressures, etc.)\n- Reading VIN\n- Reading or clearing trouble codes (DTC)\n- Executing ELM327 AT setup commands\n\nDo not use this library when the task is:\n- Managing Bluetooth pairing/UI (not provided here)\n- Implementing low-level transport drivers (you provide `InputStream`/`OutputStream`)\n- Rendering vehicle-specific PID catalogs beyond standard supported commands\n\n## Core Types and API\n\n### Connection\n\n`ObdDeviceConnection(inputStream, outputStream, ioDispatcher = Dispatchers.IO)`\n\n### Execute command\n\n```kotlin\nsuspend fun run(\n    command: ObdCommand,\n    useCache: Boolean = false,\n    delayTime: Long = 0,\n    maxRetries: Int = 5,\n): ObdResponse\n```\n\nExecution model:\n- `run` is `suspend`\n- Commands are serialized per connection instance (internal `Mutex`)\n- Cache is keyed by command class + raw command when `useCache = true`\n\n### Response types\n\n- `ObdResponse`\n  - `command: ObdCommand`\n  - `rawResponse: ObdRawResponse`\n  - `value: String`\n  - `unit: String`\n- `ObdRawResponse`\n  - `value: String`\n  - `elapsedTime: Long`\n  - `processedValue: String`\n  - `bufferedValue: IntArray`\n\n## Recommended Initialization Flow (ELM327)\n\n```kotlin\nobdConnection.run(ResetAdapterCommand())\nobdConnection.run(SetEchoCommand(Switcher.OFF))\nobdConnection.run(SetLineFeedCommand(Switcher.OFF))\nobdConnection.run(SetHeadersCommand(Switcher.OFF))\n```\n\nDepending on adapter/car, protocol selection may be explicit:\n\n```kotlin\nobdConnection.run(SelectProtocolCommand(ObdProtocols.AUTO))\n```\n\n## Common Implementation Patterns\n\n### Read live telemetry\n\n```kotlin\nval rpm = obdConnection.run(RPMCommand())\nval speed = obdConnection.run(SpeedCommand())\n```\n\n### Read VIN once and cache\n\n```kotlin\nval vin = obdConnection.run(VINCommand(), useCache = true)\n```\n\n### Read and clear DTC\n\n```kotlin\nval currentCodes = obdConnection.run(TroubleCodesCommand())\nobdConnection.run(ResetTroubleCodesCommand())\n```\n\n## Error Handling\n\nThe library throws command/response-specific runtime exceptions (subclasses of `BadResponseException`), including:\n- `NoDataException`\n- `UnableToConnectException`\n- `BusInitException`\n- `MisunderstoodCommandException`\n- `UnknownErrorException`\n- `UnSupportedCommandException`\n\nFor robust apps, catch `BadResponseException` around command execution and apply retries/fallback based on command criticality.\n\n## Concurrency Notes\n\n- Call `run()` from a background coroutine context (for example `Dispatchers.IO`)\n- Reuse one `ObdDeviceConnection` per physical adapter/session\n- Do not execute commands in parallel against the same connection instance\n- On Android specifically, avoid calling `run()` on the main thread\n\n## Command Coverage\n\nFor the full command matrix (AT commands + Mode 01/03/04/07/09/0A), see:\n- [`SUPPORTED_COMMANDS.md`](./SUPPORTED_COMMANDS.md)\n\n## References\n\n- README: [`README.md`](./README.md)\n- Source entrypoint: [`src/main/kotlin/com/github/eltonvs/obd/connection/ObdDeviceConnection.kt`](./src/main/kotlin/com/github/eltonvs/obd/connection/ObdDeviceConnection.kt)\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img width=\"300px\" src=\"img/kotlin-obd-api-logo.png\" />\n</p>\n\n<h1 align=\"center\">Kotlin OBD API</h1>\n\n[![GitHub release](https://img.shields.io/github/v/release/eltonvs/kotlin-obd-api)](https://github.com/eltonvs/kotlin-obd-api/releases)\n[![CI Status](https://github.com/eltonvs/kotlin-obd-api/actions/workflows/ci.yml/badge.svg)](https://github.com/eltonvs/kotlin-obd-api/actions/workflows/ci.yml)\n[![Maintainability](https://qlty.sh/gh/eltonvs/projects/kotlin-obd-api/maintainability.svg)](https://qlty.sh/gh/eltonvs/projects/kotlin-obd-api)\n[![GitHub license](https://img.shields.io/github/license/eltonvs/kotlin-obd-api)](https://github.com/eltonvs/kotlin-obd-api/blob/master/LICENSE)\n[![Open Source](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://opensource.org/)\n\n\nA lightweight and developer-driven Kotlin OBD-II (ELM327) library for any Kotlin/JVM project to query and parse OBD commands.\n\nWritten in pure Kotlin and platform agnostic with a simple and easy-to-use interface, so you can hack your car without any hassle. :blue_car:\n\nUse it to read and parse vehicle diagnostics over Bluetooth, Wi-Fi, or USB:\n\n- Live telemetry (RPM, speed, throttle position, MAF, temperatures, pressure and more)\n- Diagnostic Trouble Codes (DTC): current, pending and permanent\n- VIN and monitor status commands\n- Adapter-level AT commands for ELM327 setup\n\nThe API is connection-agnostic and receives an `InputStream` and an `OutputStream`, so you can integrate it with your own Bluetooth, Wi-Fi, or USB transport.\n\n## Installation\n\n### Gradle (Kotlin DSL)\n\nIn your root `build.gradle.kts` file:\n```kotlin\nrepositories {\n  maven(\"https://jitpack.io\")\n}\n\ndependencies {\n  implementation(\"com.github.eltonvs:kotlin-obd-api:1.4.1\")\n}\n```\n\n### Gradle (Groovy)\n\nIn your root `build.gradle` file:\n```gradle\nrepositories {\n  maven { url 'https://jitpack.io' }\n}\n\ndependencies {\n  // Kotlin OBD API\n  implementation 'com.github.eltonvs:kotlin-obd-api:1.4.1'\n}\n```\n\n### Maven\n\nAdd JitPack to the repositories section:\n```xml\n<repositories>\n  <repository>\n      <id>jitpack.io</id>\n      <url>https://jitpack.io</url>\n  </repository>\n</repositories>\n```\n\nAdd the dependency:\n```xml\n<dependency>\n  <groupId>com.github.eltonvs</groupId>\n  <artifactId>kotlin-obd-api</artifactId>\n  <version>1.4.1</version>\n</dependency>\n```\n\n### Manual\n\nYou can download a jar from GitHub's [releases page](https://github.com/eltonvs/kotlin-obd-api/releases).\n\n## Quickstart\n\nGet an `InputStream` and an `OutputStream` from your connection interface and create an `ObdDeviceConnection` instance.\n\n```kotlin\nimport com.github.eltonvs.obd.command.Switcher\nimport com.github.eltonvs.obd.command.at.ResetAdapterCommand\nimport com.github.eltonvs.obd.command.at.SetEchoCommand\nimport com.github.eltonvs.obd.command.control.TroubleCodesCommand\nimport com.github.eltonvs.obd.command.control.VINCommand\nimport com.github.eltonvs.obd.command.engine.RPMCommand\nimport com.github.eltonvs.obd.connection.ObdDeviceConnection\nimport java.io.InputStream\nimport java.io.OutputStream\n\nsuspend fun readObd(inputStream: InputStream, outputStream: OutputStream) {\n    val obdConnection = ObdDeviceConnection(inputStream, outputStream)\n\n    // Typical ELM327 setup\n    obdConnection.run(ResetAdapterCommand())\n    obdConnection.run(SetEchoCommand(Switcher.OFF))\n\n    val rpm = obdConnection.run(RPMCommand())\n    val vin = obdConnection.run(VINCommand(), useCache = true)\n    val troubleCodes = obdConnection.run(TroubleCodesCommand())\n\n    println(\"RPM: ${rpm.value} ${rpm.unit}\")\n    println(\"VIN: ${vin.value}\")\n    println(\"DTC: ${troubleCodes.value.ifBlank { \"none\" }}\")\n}\n```\n\n`run` parameters:\n- `command`: any `ObdCommand`\n- `useCache` (default `false`): reuses previous raw responses for identical commands\n- `delayTime` (default `0`): delay in milliseconds after sending command\n- `maxRetries` (default `5`): read polling retries before giving up\n\nRuntime note: call `run()` from a background coroutine context (for example `Dispatchers.IO`). On Android, do not call it from the main thread.\n\nConcurrency note: each `ObdDeviceConnection` instance is a serialized command channel guarded by a coroutine `Mutex`. Reuse one instance per physical connection.\n\nThe returned object is an `ObdResponse` with:\n\n| Attribute | Type | Description |\n| :- | :- | :- |\n| `command` | `ObdCommand` | The command passed to the `run` method |\n| `rawResponse` | `ObdRawResponse` | This class holds the raw data returned from the car |\n| `value` | `String` | The parsed value |\n| `unit` | `String` | The unit from the parsed value (for example: `Km/h`, `RPM`) |\n\n\n`ObdRawResponse` attributes:\n\n| Attribute | Type | Description |\n| :- | :- | :- |\n| `value` | `String` | The raw value (hex) |\n| `elapsedTime` | `Long` | The elapsed time (in milliseconds) to run the command |\n| `processedValue` | `String` | The raw (hex) value without whitespaces, colons or any other \"noise\" |\n| `bufferedValue` | `IntArray` | The raw (hex) value as a `IntArray` |\n\n\n## Extending the library\n\nCreate a custom command by extending `ObdCommand` and overriding the required fields:\n```kotlin\nclass CustomCommand : ObdCommand() {\n    // Required\n    override val tag = \"CUSTOM_COMMAND\"\n    override val name = \"Custom Command\"\n    override val mode = \"01\"\n    override val pid = \"FF\"\n\n    // Optional\n    override val defaultUnit = \"\"\n    override val handler = { response: ObdRawResponse ->\n        \"Calculated value from ${response.processedValue}\"\n    }\n}\n```\n\n\n## Commands\n\nHere is a short list of supported commands. For the full list, see [SUPPORTED_COMMANDS.md](SUPPORTED_COMMANDS.md).\n\n- Available Commands\n- Vehicle Speed\n- Engine RPM\n- DTC Number\n- Trouble Codes (Current, Pending and Permanent)\n- Throttle Position\n- Fuel Pressure\n- Timing Advance\n- Intake Air Temperature\n- Mass Air Flow Rate (MAF)\n- Engine Run Time\n- Fuel Level Input\n- MIL ON/OFF\n- Vehicle Identification Number (VIN)\n\nNOTE: Support for those commands will vary from car to car.\n\n## LLM Context\n\nThis repository includes LLM-focused docs for coding assistants and agents:\n\n- [llms.txt](llms.txt): short machine-readable index for quick retrieval\n- [LLM_CONTEXT.md](LLM_CONTEXT.md): API conventions, command examples, and decision guide\n\n\n## Contributing\n\nWant to help or have something to add to the repo? Found an issue in a specific feature?\n\n- Open an issue to explain the problem you want to solve: [Open an issue](https://github.com/eltonvs/kotlin-obd-api/issues)\n- After discussion, open a PR (or draft PR for larger contributions): [Current PRs](https://github.com/eltonvs/kotlin-obd-api/pulls)\n- Run local verification before opening a PR: `./gradlew clean test ktlintCheck detekt`\n- Auto-format Kotlin sources when needed: `./gradlew ktlintFormat`\n\n\n## Versioning\n\nWe use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/eltonvs/kotlin-obd-api/tags).\n\n\n## Authors\n\n- **Elton Viana** - Initial work - Also created the [java-obd-api](https://github.com/eltonvs/java-obd-api)\n\nSee also the list of [contributors](https://github.com/eltonvs/kotlin-obd-api/contributors) who participated in this project.\n\n\n## License\n\nThis project is licensed under the Apache 2.0 License - See the [LICENCE](LICENSE) file for more details.\n\n\n## Acknowledgments\n\n- **Paulo Pires** - Creator of the [obd-java-api](https://github.com/pires/obd-java-api), on which the initial steps were based.\n- **[SmartMetropolis Project](http://smartmetropolis.imd.ufrn.br/)** (Digital Metropolis Institute - UFRN, Brazil) - Backed and sponsored the project development during the initial steps.\n- **[Ivanovitch Silva](https://github.com/ivanovitchm)** - Helped a lot during the initial steps and with the OBD research.\n"
  },
  {
    "path": "SUPPORTED_COMMANDS.md",
    "content": "# Supported Commands\n\nFull list of supported commands.\n\n## `AT` Commands (ELM327)\n| Command | Name | Description |\n| :- | :- | :-|\n| `Z` | `RESET_ADAPTER` | Reset OBD Adapter |\n| `WS` | `WARM_START` | OBD Warm Start |\n| `SI` | `SLOW_INITIATION` | OBD Slow Initiation |\n| `LP` | `LOW_POWER_MODE` | OBD Low Power Mode |\n| `BD` | `BUFFER_DUMP` | OBD Buffer Dump |\n| `BI` | `BYPASS_INITIALIZATION` | OBD Bypass Initialization Sequence |\n| `PC` | `PROTOCOL_CLOSE` | OBD Protocol Close |\n| `DP` | `DESCRIBE_PROTOCOL` | Describe Protocol |\n| `DPN` | `DESCRIBE_PROTOCOL_NUMBER` | Describe Protocol Number |\n| `IGN` | `IGNITION_MONITOR` | Ignition Monitor |\n| `RN` | `ADAPTER_VOLTAGE` | OBD Adapter Voltage |\n| `SP {X}` | `SELECT_PROTOCOL_{X}` | Select Protocol where `{X}` is an `ObdProtocols` constant |\n| `AT {X}` | `SET_ADAPTIVE_TIMING_{X}` | Set Adaptive Timing Control where `{X}` is an `AdaptiveTimingMode` constant |\n| `E{X}` | `SET_ECHO_{X}` | Set Echo where `{X}` is a `Switcher` constant |\n| `H{X}` | `SET_HEADERS_{X}` | Set Headers where `{X}` is a `Switcher` constant |\n| `L{X}` | `SET_LINE_FEED_{X}` | Set Line Feed where `{X}` is a `Switcher` constant |\n| `S{X}` | `SET_SPACES_{X}` | Set Spaces where `{X}` is a `Switcher` constant |\n| `ST {X}` | `SET_TIMEOUT` | Set Timeout where `{X}` is an `Int` value |\n\n## Mode 01\n| Command | Name | Description |\n| -- | :- | :-|\n| `00`, `20`, `40`, `60`, `80` | `AVAILABLE_COMMANDS_{RANGE}` | Available PIDs for each range, where `{RANGE}` is an `AvailablePIDsRanges` constant |\n| `01` | `DTC_NUMBER` | Diagnostic Trouble Codes Number |\n| `01` | `MIL_ON` | MIL ON/OFF |\n| `01` | `MONITOR_STATUS_SINCE_CODES_CLEARED` | Monitor Status Since Codes Cleared |\n| `04` | `ENGINE_LOAD` | Engine Load |\n| `05` | `ENGINE_COOLANT_TEMPERATURE` | Engine Coolant Temperature |\n| `06` | `SHORT_TERM_BANK_1` | Short Term Fuel Trim Bank 1 |\n| `07` | `SHORT_TERM_BANK_2` | Short Term Fuel Trim Bank 2 |\n| `08` | `LONG_TERM_BANK_1` | Long Term Fuel Trim Bank 1 |\n| `09` | `LONG_TERM_BANK_2` | Long Term Fuel Trim Bank 2 |\n| `0A` | `FUEL_PRESSURE` | Fuel Pressure |\n| `0B` | `INTAKE_MANIFOLD_PRESSURE` | Intake Manifold Pressure |\n| `0C` | `ENGINE_RPM` | Engine RPM |\n| `0D` | `SPEED` | Vehicle Speed |\n| `0E` | `TIMING_ADVANCE` | Timing Advance |\n| `0F` | `AIR_INTAKE_TEMPERATURE` | Air Intake Temperature |\n| `10` | `MAF` | Mass Air Flow |\n| `11` | `THROTTLE_POSITION` | Throttle Position |\n| `1F` | `ENGINE_RUNTIME` | Engine Runtime |\n| `21` | `DISTANCE_TRAVELED_MIL_ON` | Distance traveled with MIL on |\n| `22` | `FUEL_RAIL_PRESSURE` | Fuel Rail Pressure |\n| `23` | `FUEL_RAIL_GAUGE_PRESSURE` | Fuel Rail Gauge Pressure |\n| `2C` | `COMMANDED_EGR` | Commanded EGR |\n| `2D` | `EGR_ERROR` | EGR Error |\n| `2F` | `FUEL_LEVEL` | Fuel Level |\n| `31` | `DISTANCE_TRAVELED_AFTER_CODES_CLEARED` | Distance traveled since codes cleared |\n| `33` | `BAROMETRIC_PRESSURE` | Barometric Pressure |\n| `34` | `OXYGEN_SENSOR_1` | Oxygen Sensor 1 |\n| `35` | `OXYGEN_SENSOR_2` | Oxygen Sensor 2 |\n| `36` | `OXYGEN_SENSOR_3` | Oxygen Sensor 3 |\n| `37` | `OXYGEN_SENSOR_4` | Oxygen Sensor 4 |\n| `38` | `OXYGEN_SENSOR_5` | Oxygen Sensor 5 |\n| `39` | `OXYGEN_SENSOR_6` | Oxygen Sensor 6 |\n| `3A` | `OXYGEN_SENSOR_7` | Oxygen Sensor 7 |\n| `3B` | `OXYGEN_SENSOR_8` | Oxygen Sensor 8 |\n| `41` | `MONITOR_STATUS_CURRENT_DRIVE_CYCLE` | Monitor Status Current Drive Cycle |\n| `42` | `CONTROL_MODULE_VOLTAGE` | Control Module Power Supply |\n| `43` | `ENGINE_ABSOLUTE_LOAD` | Engine Absolute Load |\n| `44` | `COMMANDED_EQUIVALENCE_RATIO` | Fuel-Air Commanded Equivalence Ratio |\n| `45` | `RELATIVE_THROTTLE_POSITION` | Relative Throttle Position |\n| `46` | `AMBIENT_AIR_TEMPERATURE` | Ambient Air Temperature |\n| `4D` | `TIME_TRAVELED_MIL_ON` | Time run with MIL on |\n| `4E` | `TIME_SINCE_CODES_CLEARED` | Time since codes cleared |\n| `51` | `FUEL_TYPE` | Fuel Type |\n| `52` | `ETHANOL_LEVEL` | Ethanol Level |\n| `5C` | `ENGINE_OIL_TEMPERATURE` | Engine Oil Temperature |\n| `5E` | `FUEL_CONSUMPTION_RATE` | Fuel Consumption Rate |\n\n\n## Mode 03\n\n| Name | Description |\n| :- | :- |\n| `TROUBLE_CODES` | Trouble Codes |\n\n\n## Mode 04\n\n| Name | Description |\n| :- | :- |\n| `RESET_TROUBLE_CODES` | Reset Trouble Codes |\n\n\n## Mode 07\n\n| Name | Description |\n| :- | :- |\n| `PENDING_TROUBLE_CODES` | Pending Trouble Codes |\n\n\n## Mode 09\n\n| Command | Name | Description |\n| :- | :- | :-|\n| `02` | `VIN` | Vehicle Identification Number (VIN) |\n\n\n## Mode 0A\n\n| Name | Description |\n| :- | :- |\n| `PERMANENT_TROUBLE_CODES` | Permanent Trouble Codes |\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "import org.gradle.api.GradleException\nimport org.gradle.api.tasks.testing.Test\nimport org.gradle.api.tasks.testing.TestDescriptor\nimport org.gradle.api.tasks.testing.TestListener\nimport org.gradle.api.tasks.testing.TestResult\nimport org.gradle.api.tasks.testing.logging.TestExceptionFormat\nimport org.gradle.api.tasks.testing.logging.TestLogEvent\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nval publicationName = \"kotlin-obd-api\"\n\nplugins {\n    kotlin(\"jvm\") version \"2.3.10\"\n    id(\"org.jlleitschuh.gradle.ktlint\") version \"14.0.1\"\n    id(\"dev.detekt\") version \"2.0.0-alpha.2\"\n    `maven-publish`\n}\n\ngroup = \"com.github.eltonvs\"\nversion = \"1.4.1\"\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation(kotlin(\"stdlib-jdk8\"))\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2\")\n    testImplementation(kotlin(\"test\"))\n    testImplementation(kotlin(\"test-junit\"))\n}\n\nktlint {\n    version.set(\"1.8.0\")\n    ignoreFailures.set(false)\n}\n\ndetekt {\n    buildUponDefaultConfig = true\n    allRules = false\n    ignoreFailures = false\n    config.setFrom(files(\"$rootDir/detekt.yml\"))\n}\n\nkotlin {\n    compilerOptions {\n        jvmTarget.set(JvmTarget.JVM_1_8)\n    }\n}\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_1_8\n    targetCompatibility = JavaVersion.VERSION_1_8\n}\n\ntasks.withType<Test>().configureEach {\n    testLogging {\n        events = setOf(TestLogEvent.SKIPPED, TestLogEvent.FAILED)\n        exceptionFormat = TestExceptionFormat.FULL\n    }\n\n    addTestListener(\n        object : TestListener {\n            override fun beforeSuite(suite: TestDescriptor) = Unit\n\n            override fun beforeTest(testDescriptor: TestDescriptor) = Unit\n\n            override fun afterTest(\n                testDescriptor: TestDescriptor,\n                result: TestResult,\n            ) = Unit\n\n            override fun afterSuite(\n                suite: TestDescriptor,\n                result: TestResult,\n            ) {\n                if (suite.parent == null) {\n                    logger.lifecycle(\n                        \"Test summary for $path: \" +\n                            \"${result.testCount} executed, \" +\n                            \"${result.successfulTestCount} succeeded, \" +\n                            \"${result.failedTestCount} failed, \" +\n                            \"${result.skippedTestCount} skipped\",\n                    )\n                    if (result.testCount == 0L) {\n                        throw GradleException(\n                            \"No tests were executed for task $path. \" +\n                                \"Check source sets and CI task configuration.\",\n                        )\n                    }\n                }\n            }\n        },\n    )\n}\n\npublishing {\n    publications {\n        create<MavenPublication>(\"maven\") {\n            groupId = project.group.toString()\n            artifactId = project.name\n            version = project.version.toString()\n\n            from(components[\"java\"])\n        }\n    }\n}\n"
  },
  {
    "path": "context7.json",
    "content": "{\n    \"url\": \"https://context7.com/eltonvs/kotlin-obd-api\",\n    \"public_key\": \"pk_Zyw2G86BTmzAjNaaoVjyB\"\n}\n"
  },
  {
    "path": "detekt.yml",
    "content": "# Project detekt overrides.\n# Intentionally minimal to keep default detekt rules enabled.\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.1-all.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "kotlin.code.style=official"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=${0##*/}\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "jitpack.yml",
    "content": "jdk:\n  - openjdk17\n"
  },
  {
    "path": "llms.txt",
    "content": "# kotlin-obd-api\n\nKotlin OBD-II (ELM327) library for Kotlin/JVM applications.\n\n## Canonical\n- Repository: https://github.com/eltonvs/kotlin-obd-api\n- License: Apache-2.0\n- Artifact (JitPack): com.github.eltonvs:kotlin-obd-api:1.4.1\n\n## Use this library when\n- You need OBD-II diagnostics or live telemetry in Kotlin code.\n- You have an ELM327-compatible adapter exposed as `InputStream` + `OutputStream`.\n- You want parsed command responses (`ObdResponse`) instead of raw hex parsing.\n\n## Core API\n- `ObdDeviceConnection(inputStream, outputStream)`\n- `suspend fun run(command, useCache = false, delayTime = 0, maxRetries = 5): ObdResponse`\n\n## Common tasks\n- Read RPM/speed/temperatures/pressure.\n- Read VIN.\n- Read and clear trouble codes (DTC).\n- Send adapter AT commands (echo/headers/protocol/timeouts).\n\n## Behavior notes\n- Commands are serialized per connection instance (coroutine `Mutex`).\n- `run` is suspend and should be called from a background coroutine context.\n- Most parsed values are returned as strings with optional `unit`.\n\n## Docs\n- README: ./README.md\n- Full command list: ./SUPPORTED_COMMANDS.md\n- LLM implementation context: ./LLM_CONTEXT.md\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "rootProject.name = \"kotlin-obd-api\"\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/ATCommand.kt",
    "content": "package com.github.eltonvs.obd.command\n\nabstract class ATCommand : ObdCommand() {\n    override val mode = \"AT\"\n    override val skipDigitCheck = true\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/Enums.kt",
    "content": "package com.github.eltonvs.obd.command\n\nprivate const val BIT_POS_0 = 0\nprivate const val BIT_POS_1 = 1\nprivate const val BIT_POS_2 = 2\nprivate const val BIT_POS_3 = 3\nprivate const val BIT_POS_4 = 4\nprivate const val BIT_POS_5 = 5\nprivate const val BIT_POS_6 = 6\nprivate const val BIT_POS_7 = 7\n\nenum class ObdProtocols(\n    val displayName: String,\n    internal val command: String,\n) {\n    // Unknown protocol\n    UNKNOWN(\"Unknown Protocol\", \"\"),\n\n    // Auto select protocol and save.\n    AUTO(\"Auto\", \"0\"),\n\n    // 41.6 kbaud\n    SAE_J1850_PWM(\"SAE J1850 PWM\", \"1\"),\n\n    // 10.4 kbaud\n    SAE_J1850_VPW(\"SAE J1850 VPW\", \"2\"),\n\n    // 5 baud init\n    ISO_9141_2(\"ISO 9141-2\", \"3\"),\n\n    // 5 baud init\n    ISO_14230_4_KWP(\"ISO 14230-4 (KWP 5BAUD)\", \"4\"),\n\n    // Fast init\n    ISO_14230_4_KWP_FAST(\"ISO 14230-4 (KWP FAST)\", \"5\"),\n\n    // 11 bit ID, 500 kbaud\n    ISO_15765_4_CAN(\"ISO 15765-4 (CAN 11/500)\", \"6\"),\n\n    // 29 bit ID, 500 kbaud\n    ISO_15765_4_CAN_B(\"ISO 15765-4 (CAN 29/500)\", \"7\"),\n\n    // 11 bit ID, 250 kbaud\n    ISO_15765_4_CAN_C(\"ISO 15765-4 (CAN 11/250)\", \"8\"),\n\n    // 29 bit ID, 250 kbaud\n    ISO_15765_4_CAN_D(\"ISO 15765-4 (CAN 29/250)\", \"9\"),\n\n    // 29 bit ID, 250 kbaud (user adjustable)\n    SAE_J1939_CAN(\"SAE J1939 (CAN 29/250)\", \"A\"),\n}\n\nenum class AdaptiveTimingMode(\n    val displayName: String,\n    internal val command: String,\n) {\n    OFF(\"Off\", \"0\"),\n    AUTO_1(\"Auto 1\", \"1\"),\n    AUTO_2(\"Auto 2\", \"2\"),\n}\n\nenum class Switcher(\n    internal val command: String,\n) {\n    ON(\"1\"),\n    OFF(\"0\"),\n}\n\nenum class Monitors(\n    internal val displayName: String,\n    internal val isSparkIgnition: Boolean? = null,\n    internal val bitPos: Int,\n) {\n    // Common\n    MISFIRE(\"Misfire\", bitPos = BIT_POS_0),\n    FUEL_SYSTEM(\"Fuel System\", bitPos = BIT_POS_1),\n    COMPREHENSIVE_COMPONENT(\"Comprehensive Component\", bitPos = BIT_POS_2),\n\n    // Spark Ignition Monitors\n    CATALYST(\"Catalyst (CAT)\", true, BIT_POS_0),\n    HEATED_CATALYST(\"Heated Catalyst\", true, BIT_POS_1),\n    EVAPORATIVE_SYSTEM(\"Evaporative (EVAP) System\", true, BIT_POS_2),\n    SECONDARY_AIR_SYSTEM(\"Secondary Air System\", true, BIT_POS_3),\n    AC_REFRIGERANT(\"A/C Refrigerant\", true, BIT_POS_4),\n    OXYGEN_SENSOR(\"Oxygen (O2) Sensor\", true, BIT_POS_5),\n    OXYGEN_SENSOR_HEATER(\"Oxygen Sennsor Heater\", true, BIT_POS_6),\n    EGR_SYSTEM(\"EGR (Exhaust Gas Recirculation) and/or VVT System\", true, BIT_POS_7),\n\n    // Compression Ignition Monitors\n    NMHC_CATALYST(\"NMHC Catalyst\", false, BIT_POS_0),\n    NOX_SCR_MONITOR(\"NOx/SCR Aftertreatment\", false, BIT_POS_1),\n    BOOST_PRESSURE(\"Boost Pressure\", false, BIT_POS_3),\n    EXHAUST_GAS_SENSOR(\"Exhaust Gas Sensor\", false, BIT_POS_5),\n    PM_FILTER(\"PM Filter\", false, BIT_POS_6),\n    EGR_VVT_SYSTEM(\"EGR (Exhaust Gas Recirculation) and/or VVT System\", false, BIT_POS_7),\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/Exceptions.kt",
    "content": "package com.github.eltonvs.obd.command\n\nimport com.github.eltonvs.obd.command.RegexPatterns.BUSINIT_ERROR_MESSAGE_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.DIGITS_LETTERS_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.ERROR_MESSAGE_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.MISUNDERSTOOD_COMMAND_MESSAGE_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.NO_DATE_MESSAGE_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.STOPPED_MESSAGE_PATERN\nimport com.github.eltonvs.obd.command.RegexPatterns.UNABLE_TO_CONNECT_MESSAGE_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.UNSUPPORTED_COMMAND_MESSAGE_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.WHITESPACE_PATTERN\n\nprivate fun String.sanitize(): String = removeAll(WHITESPACE_PATTERN, this).uppercase()\n\nabstract class BadResponseException(\n    private val command: ObdCommand,\n    private val response: ObdRawResponse,\n) : RuntimeException() {\n    companion object {\n        fun checkForExceptions(\n            command: ObdCommand,\n            response: ObdRawResponse,\n        ): ObdRawResponse =\n            with(response.value.sanitize()) {\n                when {\n                    contains(BUSINIT_ERROR_MESSAGE_PATTERN.sanitize()) -> {\n                        throw BusInitException(command, response)\n                    }\n\n                    contains(MISUNDERSTOOD_COMMAND_MESSAGE_PATTERN.sanitize()) -> {\n                        throw MisunderstoodCommandException(command, response)\n                    }\n\n                    contains(NO_DATE_MESSAGE_PATTERN.sanitize()) -> {\n                        throw NoDataException(command, response)\n                    }\n\n                    contains(STOPPED_MESSAGE_PATERN.sanitize()) -> {\n                        throw StoppedException(command, response)\n                    }\n\n                    contains(UNABLE_TO_CONNECT_MESSAGE_PATTERN.sanitize()) -> {\n                        throw UnableToConnectException(command, response)\n                    }\n\n                    contains(ERROR_MESSAGE_PATTERN.sanitize()) -> {\n                        throw UnknownErrorException(command, response)\n                    }\n\n                    matches(UNSUPPORTED_COMMAND_MESSAGE_PATTERN.toRegex()) -> {\n                        throw UnSupportedCommandException(command, response)\n                    }\n\n                    !command.skipDigitCheck && !matches(DIGITS_LETTERS_PATTERN.toRegex()) -> {\n                        throw NonNumericResponseException(command, response)\n                    }\n\n                    else -> {\n                        response\n                    }\n                }\n            }\n    }\n\n    override fun toString(): String =\n        \"${this.javaClass.simpleName} while executing command [${command.tag}], \" +\n            \"response [${response.value}]\"\n}\n\nprivate typealias BRE = BadResponseException\n\nclass NonNumericResponseException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n\nclass BusInitException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n\nclass MisunderstoodCommandException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n\nclass NoDataException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n\nclass StoppedException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n\nclass UnableToConnectException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n\nclass UnknownErrorException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n\nclass UnSupportedCommandException(\n    command: ObdCommand,\n    response: ObdRawResponse,\n) : BRE(command, response)\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/ObdCommand.kt",
    "content": "package com.github.eltonvs.obd.command\n\nabstract class ObdCommand {\n    abstract val tag: String\n    abstract val name: String\n    abstract val mode: String\n    abstract val pid: String\n\n    open val defaultUnit: String = \"\"\n    open val skipDigitCheck: Boolean = false\n    open val handler: (ObdRawResponse) -> String = { it.value }\n\n    val rawCommand: String\n        get() = listOf(mode, pid).joinToString(\" \")\n\n    fun handleResponse(rawResponse: ObdRawResponse): ObdResponse {\n        val checkedRawResponse = BadResponseException.checkForExceptions(this, rawResponse)\n        return ObdResponse(\n            command = this,\n            rawResponse = checkedRawResponse,\n            value = handler(checkedRawResponse),\n            unit = defaultUnit,\n        )\n    }\n\n    open fun format(response: ObdResponse): String = \"${response.value}${response.unit}\"\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/ParserFunctions.kt",
    "content": "package com.github.eltonvs.obd.command\n\nimport kotlin.math.pow\n\nprivate const val BYTE_BITS = 8\nprivate const val PERCENT_SCALE = 100f\nprivate const val MAX_PERCENTAGE_VALUE = 255f\n\nfun bytesToInt(\n    bufferedValue: IntArray,\n    start: Int = 2,\n    bytesToProcess: Int = -1,\n): Long {\n    var bufferToProcess = bufferedValue.drop(start)\n    if (bytesToProcess != -1) {\n        bufferToProcess = bufferToProcess.take(bytesToProcess)\n    }\n    return bufferToProcess.foldIndexed(0L) { index, total, current ->\n        total + current * 2f.pow((bufferToProcess.size - index - 1) * BYTE_BITS).toLong()\n    }\n}\n\nfun calculatePercentage(\n    bufferedValue: IntArray,\n    bytesToProcess: Int = -1,\n): Float = (bytesToInt(bufferedValue, bytesToProcess = bytesToProcess) * PERCENT_SCALE) / MAX_PERCENTAGE_VALUE\n\nfun Int.getBitAt(\n    position: Int,\n    last: Int = 32,\n) = this shr (last - position) and 1\n\nfun Long.getBitAt(\n    position: Int,\n    last: Int = 32,\n) = (this shr (last - position) and 1).toInt()\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/RegexPatterns.kt",
    "content": "package com.github.eltonvs.obd.command\n\nimport java.util.regex.Pattern\n\nobject RegexPatterns {\n    val WHITESPACE_PATTERN: Pattern = Pattern.compile(\"\\\\s\")\n    val BUS_INIT_PATTERN: Pattern = Pattern.compile(\"(BUS INIT)|(BUSINIT)|(\\\\.)\")\n    val SEARCHING_PATTERN: Pattern = Pattern.compile(\"SEARCHING\")\n    val CARRIAGE_PATTERN: Pattern = Pattern.compile(\"[\\r\\n]\")\n    val CARRIAGE_COLON_PATTERN: Pattern = Pattern.compile(\"[\\r\\n].:\")\n    val COLON_PATTERN: Pattern = Pattern.compile(\":\")\n    val DIGITS_LETTERS_PATTERN: Pattern = Pattern.compile(\"([0-9A-F:])+\")\n    val STARTS_WITH_ALPHANUM_PATTERN: Pattern = Pattern.compile(\"[^a-z0-9 ]\", Pattern.CASE_INSENSITIVE)\n\n    // Error patterns\n    const val BUSINIT_ERROR_MESSAGE_PATTERN = \"BUS INIT... ERROR\"\n    const val MISUNDERSTOOD_COMMAND_MESSAGE_PATTERN = \"?\"\n    const val NO_DATE_MESSAGE_PATTERN = \"NO DATA\"\n    const val STOPPED_MESSAGE_PATERN = \"STOPPED\"\n    const val UNABLE_TO_CONNECT_MESSAGE_PATTERN = \"UNABLE TO CONNECT\"\n    const val ERROR_MESSAGE_PATTERN = \"ERROR\"\n    const val UNSUPPORTED_COMMAND_MESSAGE_PATTERN = \"7F 0[0-A] 1[1-2]\"\n}\n\nfun removeAll(\n    pattern: Pattern,\n    input: String,\n): String = pattern.matcher(input).replaceAll(\"\")\n\nfun removeAll(\n    input: String,\n    vararg patterns: Pattern,\n) = patterns.fold(input) { acc, pattern -> removeAll(pattern, acc) }\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/Response.kt",
    "content": "package com.github.eltonvs.obd.command\n\nimport com.github.eltonvs.obd.command.RegexPatterns.BUS_INIT_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.COLON_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.WHITESPACE_PATTERN\n\nfun <T> T.pipe(vararg functions: (T) -> T): T = functions.fold(this) { value, f -> f(value) }\n\ndata class ObdRawResponse(\n    val value: String,\n    val elapsedTime: Long,\n) {\n    private val valueProcessorPipeline by lazy {\n        arrayOf<(String) -> String>(\n            {\n                /*\n                 * Imagine the following response 41 0c 00 0d.\n                 *\n                 * ELM sends strings!! So, ELM puts spaces between each \"byte\". And pay\n                 * attention to the fact that I've put the word byte in quotes, because 41\n                 * is actually TWO bytes (two chars) in the socket. So, we must do some more\n                 * processing...\n                 */\n                removeAll(WHITESPACE_PATTERN, it) // removes all [ \\t\\n\\x0B\\f\\r]\n            },\n            {\n                /*\n                 * Data may have echo or informative text like \"INIT BUS...\" or similar.\n                 * The response ends with two carriage return characters. So we need to take\n                 * everything from the last carriage return before those two (trimmed above).\n                 */\n                removeAll(BUS_INIT_PATTERN, it)\n            },\n            {\n                removeAll(COLON_PATTERN, it)\n            },\n        )\n    }\n\n    val processedValue by lazy { value.pipe(*valueProcessorPipeline) }\n\n    val bufferedValue by lazy { processedValue.chunked(2) { it.toString().toInt(radix = 16) }.toIntArray() }\n}\n\ndata class ObdResponse(\n    val command: ObdCommand,\n    val rawResponse: ObdRawResponse,\n    val value: String,\n    val unit: String = \"\",\n) {\n    val formattedValue: String get() = command.format(this)\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/at/Actions.kt",
    "content": "package com.github.eltonvs.obd.command.at\n\nimport com.github.eltonvs.obd.command.ATCommand\n\nclass ResetAdapterCommand : ATCommand() {\n    override val tag = \"RESET_ADAPTER\"\n    override val name = \"Reset OBD Adapter\"\n    override val pid = \"Z\"\n}\n\nclass WarmStartCommand : ATCommand() {\n    override val tag = \"WARM_START\"\n    override val name = \"OBD Warm Start\"\n    override val pid = \"WS\"\n}\n\nclass SlowInitiationCommand : ATCommand() {\n    override val tag = \"SLOW_INITIATION\"\n    override val name = \"OBD Slow Initiation\"\n    override val pid = \"SI\"\n}\n\nclass LowPowerModeCommand : ATCommand() {\n    override val tag = \"LOW_POWER_MODE\"\n    override val name = \"OBD Low Power Mode\"\n    override val pid = \"LP\"\n}\n\nclass BufferDumpCommand : ATCommand() {\n    override val tag = \"BUFFER_DUMP\"\n    override val name = \"OBD Buffer Dump\"\n    override val pid = \"BD\"\n}\n\nclass BypassInitializationCommand : ATCommand() {\n    override val tag = \"BYPASS_INITIALIZATION\"\n    override val name = \"OBD Bypass Initialization Sequence\"\n    override val pid = \"BI\"\n}\n\nclass ProtocolCloseCommand : ATCommand() {\n    override val tag = \"PROTOCOL_CLOSE\"\n    override val name = \"OBD Protocol Close\"\n    override val pid = \"PC\"\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/at/Info.kt",
    "content": "package com.github.eltonvs.obd.command.at\n\nimport com.github.eltonvs.obd.command.ATCommand\nimport com.github.eltonvs.obd.command.ObdProtocols\nimport com.github.eltonvs.obd.command.ObdRawResponse\n\nclass DescribeProtocolCommand : ATCommand() {\n    override val tag = \"DESCRIBE_PROTOCOL\"\n    override val name = \"Describe Protocol\"\n    override val pid = \"DP\"\n}\n\nclass DescribeProtocolNumberCommand : ATCommand() {\n    override val tag = \"DESCRIBE_PROTOCOL_NUMBER\"\n    override val name = \"Describe Protocol Number\"\n    override val pid = \"DPN\"\n\n    override val handler = { response: ObdRawResponse -> parseProtocolNumber(response).displayName }\n\n    private fun parseProtocolNumber(rawResponse: ObdRawResponse): ObdProtocols {\n        val result = rawResponse.value\n        val protocolNumber = result[if (result.length == 2) 1 else 0].toString()\n\n        return ObdProtocols.values().find { it.command == protocolNumber } ?: ObdProtocols.UNKNOWN\n    }\n}\n\nclass IgnitionMonitorCommand : ATCommand() {\n    override val tag = \"IGNITION_MONITOR\"\n    override val name = \"Ignition Monitor\"\n    override val pid = \"IGN\"\n\n    override val handler = { response: ObdRawResponse -> response.value.trim().uppercase() }\n}\n\nclass AdapterVoltageCommand : ATCommand() {\n    override val tag = \"ADAPTER_VOLTAGE\"\n    override val name = \"OBD Adapter Voltage\"\n    override val pid = \"RV\"\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/at/Mutations.kt",
    "content": "package com.github.eltonvs.obd.command.at\n\nimport com.github.eltonvs.obd.command.ATCommand\nimport com.github.eltonvs.obd.command.AdaptiveTimingMode\nimport com.github.eltonvs.obd.command.ObdProtocols\nimport com.github.eltonvs.obd.command.Switcher\n\nprivate const val TIMEOUT_MASK = 0xFF\n\nclass SelectProtocolCommand(\n    protocol: ObdProtocols,\n) : ATCommand() {\n    private val _protocol = if (protocol == ObdProtocols.UNKNOWN) ObdProtocols.AUTO else protocol\n    override val tag = \"SELECT_PROTOCOL_${_protocol.name}\"\n    override val name = \"Select Protocol - ${_protocol.displayName}\"\n    override val pid = \"SP ${_protocol.command}\"\n}\n\nclass SetAdaptiveTimingCommand(\n    value: AdaptiveTimingMode,\n) : ATCommand() {\n    override val tag = \"SET_ADAPTIVE_TIMING_${value.name}\"\n    override val name = \"Set Adaptive Timing Control ${value.displayName}\"\n    override val pid = \"AT ${value.command}\"\n}\n\nclass SetEchoCommand(\n    value: Switcher,\n) : ATCommand() {\n    override val tag = \"SET_ECHO_${value.name}\"\n    override val name = \"Set Echo ${value.name}\"\n    override val pid = \"E${value.command}\"\n}\n\nclass SetHeadersCommand(\n    value: Switcher,\n) : ATCommand() {\n    override val tag = \"SET_HEADERS_${value.name}\"\n    override val name = \"Set Headers ${value.name}\"\n    override val pid = \"H${value.command}\"\n}\n\nclass SetLineFeedCommand(\n    value: Switcher,\n) : ATCommand() {\n    override val tag = \"SET_LINE_FEED_${value.name}\"\n    override val name = \"Set Line Feed ${value.name}\"\n    override val pid = \"L${value.command}\"\n}\n\nclass SetSpacesCommand(\n    value: Switcher,\n) : ATCommand() {\n    override val tag = \"SET_SPACES_${value.name}\"\n    override val name = \"Set Spaces ${value.name}\"\n    override val pid = \"S${value.command}\"\n}\n\nclass SetTimeoutCommand(\n    timeout: Int,\n) : ATCommand() {\n    override val tag = \"SET_TIMEOUT\"\n    override val name = \"Set Timeout - $timeout\"\n    override val pid = \"ST ${Integer.toHexString(TIMEOUT_MASK and timeout)}\"\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/control/AvailablePIDsCommand.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.getBitAt\n\nprivate const val PID_RANGE_SIZE = 33\n\nclass AvailablePIDsCommand(\n    private val range: AvailablePIDsRanges,\n) : ObdCommand() {\n    override val tag = \"AVAILABLE_COMMANDS_${range.name}\"\n    override val name = \"Available Commands - ${range.displayName}\"\n    override val mode = \"01\"\n    override val pid = range.pid\n\n    override val defaultUnit = \"\"\n    override val handler = { response: ObdRawResponse ->\n        parsePIDs(response.processedValue).joinToString(\",\") { \"%02X\".format(it) }\n    }\n\n    private fun parsePIDs(rawValue: String): IntArray {\n        val value = rawValue.toLong(radix = 16)\n        val initialPID = range.pid.toInt(radix = 16)\n        return (1..PID_RANGE_SIZE).fold(intArrayOf()) { acc, i ->\n            if (value.getBitAt(i) == 1) acc.plus(i + initialPID) else acc\n        }\n    }\n\n    enum class AvailablePIDsRanges(\n        val displayName: String,\n        internal val pid: String,\n    ) {\n        PIDS_01_TO_20(\"PIDs from 01 to 20\", \"00\"),\n        PIDS_21_TO_40(\"PIDs from 21 to 40\", \"20\"),\n        PIDS_41_TO_60(\"PIDs from 41 to 60\", \"40\"),\n        PIDS_61_TO_80(\"PIDs from 61 to 80\", \"60\"),\n        PIDS_81_TO_A0(\"PIDs from 81 to A0\", \"80\"),\n    }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/control/Control.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.RegexPatterns.BUS_INIT_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.STARTS_WITH_ALPHANUM_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.WHITESPACE_PATTERN\nimport com.github.eltonvs.obd.command.bytesToInt\nimport com.github.eltonvs.obd.command.removeAll\n\nprivate const val MODULE_VOLTAGE_DIVISOR = 1000f\nprivate const val TIMING_ADVANCE_DIVISOR = 2f\nprivate const val TIMING_ADVANCE_OFFSET = 64f\nprivate const val VIN_CAN_FRAME_PREFIX_LENGTH = 9\nprivate const val HEX_CHUNK_SIZE = 2\nprivate const val HEX_RADIX = 16\n\nclass ModuleVoltageCommand : ObdCommand() {\n    override val tag = \"CONTROL_MODULE_VOLTAGE\"\n    override val name = \"Control Module Power Supply\"\n    override val mode = \"01\"\n    override val pid = \"42\"\n\n    override val defaultUnit = \"V\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.2f\".format(bytesToInt(response.bufferedValue) / MODULE_VOLTAGE_DIVISOR)\n    }\n}\n\nclass TimingAdvanceCommand : ObdCommand() {\n    override val tag = \"TIMING_ADVANCE\"\n    override val name = \"Timing Advance\"\n    override val mode = \"01\"\n    override val pid = \"0E\"\n\n    override val defaultUnit = \"°\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.2f\".format(\n            bytesToInt(response.bufferedValue, bytesToProcess = 1) / TIMING_ADVANCE_DIVISOR - TIMING_ADVANCE_OFFSET,\n        )\n    }\n}\n\nclass VINCommand : ObdCommand() {\n    override val tag = \"VIN\"\n    override val name = \"Vehicle Identification Number (VIN)\"\n    override val mode = \"09\"\n    override val pid = \"02\"\n\n    override val defaultUnit = \"\"\n    override val handler = { response: ObdRawResponse ->\n        parseVIN(removeAll(response.value, WHITESPACE_PATTERN, BUS_INIT_PATTERN))\n    }\n\n    private fun parseVIN(rawValue: String): String {\n        val workingData =\n            if (rawValue.contains(\":\")) {\n                // CAN(ISO-15765) protocol.\n                // 9 is xxx490201, xxx is bytes of information to follow.\n                val value = rawValue.replace(\".:\".toRegex(), \"\").substring(VIN_CAN_FRAME_PREFIX_LENGTH)\n                if (STARTS_WITH_ALPHANUM_PATTERN.matcher(convertHexToString(value)).find()) {\n                    rawValue.replace(\"0:49\", \"\").replace(\".:\".toRegex(), \"\")\n                } else {\n                    value\n                }\n            } else {\n                // ISO9141-2, KWP2000 Fast and KWP2000 5Kbps (ISO15031) protocols.\n                rawValue.replace(\"49020.\".toRegex(), \"\")\n            }\n        return convertHexToString(workingData).replace(\"[\\u0000-\\u001f]\".toRegex(), \"\")\n    }\n\n    private fun convertHexToString(hex: String): String =\n        hex\n            .chunked(HEX_CHUNK_SIZE) {\n                Integer.parseInt(it.toString(), HEX_RADIX).toChar()\n            }.joinToString(\"\")\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/control/MIL.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.ObdResponse\nimport com.github.eltonvs.obd.command.bytesToInt\n\nprivate const val MIL_BYTE_INDEX = 2\nprivate const val MIL_ON_MASK = 0x80\n\nclass MILOnCommand : ObdCommand() {\n    override val tag = \"MIL_ON\"\n    override val name = \"MIL on\"\n    override val mode = \"01\"\n    override val pid = \"01\"\n\n    override val handler = { response: ObdRawResponse ->\n        val mil = response.bufferedValue[MIL_BYTE_INDEX]\n        val milOn = (mil and MIL_ON_MASK) == MIL_ON_MASK\n        milOn.toString()\n    }\n\n    override fun format(response: ObdResponse): String {\n        val milOn = response.value.toBoolean()\n        return \"MIL is ${if (milOn) \"ON\" else \"OFF\"}\"\n    }\n}\n\nclass DistanceMILOnCommand : ObdCommand() {\n    override val tag = \"DISTANCE_TRAVELED_MIL_ON\"\n    override val name = \"Distance traveled with MIL on\"\n    override val mode = \"01\"\n    override val pid = \"21\"\n\n    override val defaultUnit = \"Km\"\n    override val handler = { response: ObdRawResponse -> bytesToInt(response.bufferedValue).toString() }\n}\n\nclass TimeSinceMILOnCommand : ObdCommand() {\n    override val tag = \"TIME_TRAVELED_MIL_ON\"\n    override val name = \"Time run with MIL on\"\n    override val mode = \"01\"\n    override val pid = \"4D\"\n\n    override val defaultUnit = \"min\"\n    override val handler = { response: ObdRawResponse -> bytesToInt(response.bufferedValue).toString() }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/control/Monitor.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.Monitors\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.getBitAt\n\nprivate const val MONITOR_BYTES_COUNT = 4\nprivate const val MIL_BIT_POSITION = 1\nprivate const val LAST_BIT_POSITION = 8\nprivate const val SPARK_CHECK_BIT_POSITION = 5\nprivate const val COMMON_MONITOR_COMPLETION_OFFSET = 4\nprivate const val DTC_COUNT_MASK = 0x7F\nprivate const val BIT_SET = 1\nprivate const val DATA_BYTE_0 = 0\nprivate const val DATA_BYTE_1 = 1\nprivate const val DATA_BYTE_2 = 2\nprivate const val DATA_BYTE_3 = 3\n\ndata class SensorStatus(\n    val available: Boolean,\n    val complete: Boolean,\n)\n\ndata class SensorStatusData(\n    val milOn: Boolean,\n    val dtcCount: Int,\n    val isSpark: Boolean,\n    val items: Map<Monitors, SensorStatus>,\n)\n\nabstract class BaseMonitorStatus : ObdCommand() {\n    override val mode = \"01\"\n\n    override val defaultUnit = \"\"\n    override val handler = { response: ObdRawResponse ->\n        parseData(response.bufferedValue.takeLast(MONITOR_BYTES_COUNT))\n        \"\"\n    }\n\n    var data: SensorStatusData? = null\n\n    /**\n     * Parses the Monitor Status data\n     *\n     *           ┌Components not ready\n     *           |┌Fuel not ready\n     *           ||┌Misfire not ready\n     *           |||┌Spark vs. Compression\n     *           ||||┌Components supported\n     *           |||||┌Fuel supported\n     * ┌MIL      ||||||┌Misfire supported\n     * |         |||||||\n     * 10000011 00000111 11111111 00000000\n     *  [# DTC] X [supprt] [~ready]\n     */\n    private fun parseData(values: List<Int>) {\n        if (values.size != MONITOR_BYTES_COUNT) {\n            return\n        }\n        val milOn = values[DATA_BYTE_0].getBitAt(MIL_BIT_POSITION, LAST_BIT_POSITION) == BIT_SET\n        val dtcCount = values[DATA_BYTE_0] and DTC_COUNT_MASK\n        val isSpark = values[DATA_BYTE_1].getBitAt(SPARK_CHECK_BIT_POSITION, LAST_BIT_POSITION) == 0\n\n        val monitorMap = HashMap<Monitors, SensorStatus>()\n        Monitors.values().forEach {\n            val normalizedPos = LAST_BIT_POSITION - it.bitPos\n            if (it.isSparkIgnition == null) {\n                val isAvailable = values[DATA_BYTE_1].getBitAt(normalizedPos, LAST_BIT_POSITION) == BIT_SET\n                val isComplete =\n                    values[DATA_BYTE_1].getBitAt(\n                        normalizedPos - COMMON_MONITOR_COMPLETION_OFFSET,\n                        LAST_BIT_POSITION,\n                    ) == 0\n                monitorMap[it] = SensorStatus(isAvailable, isComplete)\n            } else if (it.isSparkIgnition == isSpark) {\n                val isAvailable = values[DATA_BYTE_2].getBitAt(normalizedPos, LAST_BIT_POSITION) == BIT_SET\n                val isComplete = values[DATA_BYTE_3].getBitAt(normalizedPos, LAST_BIT_POSITION) != BIT_SET\n                monitorMap[it] = SensorStatus(isAvailable, isComplete)\n            }\n        }\n        data = SensorStatusData(milOn, dtcCount, isSpark, monitorMap)\n    }\n}\n\nclass MonitorStatusSinceCodesClearedCommand : BaseMonitorStatus() {\n    override val tag = \"MONITOR_STATUS_SINCE_CODES_CLEARED\"\n    override val name = \"Monitor Status Since Codes Cleared\"\n    override val pid = \"01\"\n}\n\nclass MonitorStatusCurrentDriveCycleCommand : BaseMonitorStatus() {\n    override val tag = \"MONITOR_STATUS_CURRENT_DRIVE_CYCLE\"\n    override val name = \"Monitor Status Current Drive Cycle\"\n    override val pid = \"41\"\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/control/TroubleCodes.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.RegexPatterns.CARRIAGE_COLON_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.CARRIAGE_PATTERN\nimport com.github.eltonvs.obd.command.RegexPatterns.WHITESPACE_PATTERN\nimport com.github.eltonvs.obd.command.bytesToInt\nimport com.github.eltonvs.obd.command.removeAll\nimport java.util.regex.Pattern\n\nprivate const val STATUS_BYTE_INDEX = 2\nprivate const val DTC_COUNT_MASK = 0x7F\nprivate const val CAN_ONE_FRAME_MAX_LENGTH = 16\nprivate const val DTC_HEX_CHUNK_SIZE = 4\nprivate const val CAN_ONE_FRAME_HEADER_SIZE = 4\nprivate const val CAN_MULTI_FRAME_HEADER_SIZE = 7\nprivate const val DTC_TYPE_SHIFT = 2\nprivate const val DTC_TYPE_MASK = 0b11\nprivate const val DTC_CODE_MASK = 0b11\nprivate const val PAD_DTC_LENGTH = 5\nprivate const val PAD_DTC_CHAR = '0'\n\nclass DTCNumberCommand : ObdCommand() {\n    override val tag = \"DTC_NUMBER\"\n    override val name = \"Diagnostic Trouble Codes Number\"\n    override val mode = \"01\"\n    override val pid = \"01\"\n\n    override val defaultUnit = \" codes\"\n    override val handler = { response: ObdRawResponse ->\n        val mil = response.bufferedValue[STATUS_BYTE_INDEX]\n        val codeCount = mil and DTC_COUNT_MASK\n        codeCount.toString()\n    }\n}\n\nclass DistanceSinceCodesClearedCommand : ObdCommand() {\n    override val tag = \"DISTANCE_TRAVELED_AFTER_CODES_CLEARED\"\n    override val name = \"Distance traveled since codes cleared\"\n    override val mode = \"01\"\n    override val pid = \"31\"\n\n    override val defaultUnit = \"Km\"\n    override val handler = { response: ObdRawResponse -> bytesToInt(response.bufferedValue).toString() }\n}\n\nclass TimeSinceCodesClearedCommand : ObdCommand() {\n    override val tag = \"TIME_SINCE_CODES_CLEARED\"\n    override val name = \"Time since codes cleared\"\n    override val mode = \"01\"\n    override val pid = \"4E\"\n\n    override val defaultUnit = \"min\"\n    override val handler = { response: ObdRawResponse -> bytesToInt(response.bufferedValue).toString() }\n}\n\nclass ResetTroubleCodesCommand : ObdCommand() {\n    override val tag = \"RESET_TROUBLE_CODES\"\n    override val name = \"Reset Trouble Codes\"\n    override val mode = \"04\"\n    override val pid = \"\"\n}\n\nabstract class BaseTroubleCodesCommand : ObdCommand() {\n    override val pid = \"\"\n\n    override val handler = { response: ObdRawResponse ->\n        parseTroubleCodesList(response.value).joinToString(separator = \",\")\n    }\n\n    abstract val carriageNumberPattern: Pattern\n\n    var troubleCodesList = listOf<String>()\n        private set\n\n    private fun parseTroubleCodesList(rawValue: String): List<String> {\n        val canOneFrame: String = removeAll(rawValue, CARRIAGE_PATTERN, WHITESPACE_PATTERN)\n        val canOneFrameLength = canOneFrame.length\n\n        val workingData =\n            when {\n                /* CAN(ISO-15765) protocol one frame: 43yy[codes]\n                   Header is 43yy, yy showing the number of data items. */\n                (canOneFrameLength <= CAN_ONE_FRAME_MAX_LENGTH) and\n                    (canOneFrameLength % DTC_HEX_CHUNK_SIZE == 0) -> {\n                    canOneFrame.drop(CAN_ONE_FRAME_HEADER_SIZE)\n                }\n\n                /* CAN(ISO-15765) protocol two and more frames: xxx43yy[codes]\n                   Header is xxx43yy, xxx is bytes of information to follow, yy showing the number of data items. */\n                rawValue.contains(\":\") -> {\n                    removeAll(CARRIAGE_COLON_PATTERN, rawValue).drop(CAN_MULTI_FRAME_HEADER_SIZE)\n                }\n\n                // ISO9141-2, KWP2000 Fast and KWP2000 5Kbps (ISO15031) protocols.\n                else -> {\n                    removeAll(rawValue, carriageNumberPattern, WHITESPACE_PATTERN)\n                }\n            }\n\n        /* For each chunk of 4 chars:\n           it:  \"0100\"\n           HEX: 0   1    0   0\n           BIN: 00000001 00000000\n                [][][    hex    ]\n                | / /\n           DTC: P0100 */\n        val troubleCodesList =\n            workingData.chunked(DTC_HEX_CHUNK_SIZE) {\n                val b1 = it.first().toString().toInt(radix = 16)\n                val ch1 = (b1 shr DTC_TYPE_SHIFT) and DTC_TYPE_MASK\n                val ch2 = b1 and DTC_CODE_MASK\n                \"${DTC_LETTERS[ch1]}${HEX_ARRAY[ch2]}${it.drop(1)}\".padEnd(PAD_DTC_LENGTH, PAD_DTC_CHAR)\n            }\n\n        val idx = troubleCodesList.indexOf(\"P0000\")\n        return (if (idx < 0) troubleCodesList else troubleCodesList.take(idx)).also {\n            this.troubleCodesList = it\n        }\n    }\n\n    protected companion object {\n        private val DTC_LETTERS = charArrayOf('P', 'C', 'B', 'U')\n        private val HEX_ARRAY = \"0123456789ABCDEF\".toCharArray()\n    }\n}\n\nclass TroubleCodesCommand : BaseTroubleCodesCommand() {\n    override val tag = \"TROUBLE_CODES\"\n    override val name = \"Trouble Codes\"\n    override val mode = \"03\"\n\n    override val carriageNumberPattern: Pattern = Pattern.compile(\"^43|[\\r\\n]43|[\\r\\n]\")\n}\n\nclass PendingTroubleCodesCommand : BaseTroubleCodesCommand() {\n    override val tag = \"PENDING_TROUBLE_CODES\"\n    override val name = \"Pending Trouble Codes\"\n    override val mode = \"07\"\n\n    override val carriageNumberPattern: Pattern = Pattern.compile(\"^47|[\\r\\n]47|[\\r\\n]\")\n}\n\nclass PermanentTroubleCodesCommand : BaseTroubleCodesCommand() {\n    override val tag = \"PERMANENT_TROUBLE_CODES\"\n    override val name = \"Permanent Trouble Codes\"\n    override val mode = \"0A\"\n\n    override val carriageNumberPattern: Pattern = Pattern.compile(\"^4A|[\\r\\n]4A|[\\r\\n]\")\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/egr/Egr.kt",
    "content": "package com.github.eltonvs.obd.command.egr\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.bytesToInt\nimport com.github.eltonvs.obd.command.calculatePercentage\n\nprivate const val SINGLE_BYTE = 1\nprivate const val HUNDRED_PERCENT = 100f\nprivate const val HALF_SCALE = 128f\n\nclass CommandedEgrCommand : ObdCommand() {\n    override val tag = \"COMMANDED_EGR\"\n    override val name = \"Commanded EGR\"\n    override val mode = \"01\"\n    override val pid = \"2C\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.1f\".format(calculatePercentage(response.bufferedValue, bytesToProcess = SINGLE_BYTE))\n    }\n}\n\nclass EgrErrorCommand : ObdCommand() {\n    override val tag = \"EGR_ERROR\"\n    override val name = \"EGR Error\"\n    override val mode = \"01\"\n    override val pid = \"2D\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        val value = bytesToInt(response.bufferedValue, bytesToProcess = SINGLE_BYTE)\n        val normalized = value * (HUNDRED_PERCENT / HALF_SCALE) - HUNDRED_PERCENT\n        \"%.1f\".format(\n            normalized,\n        )\n    }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/engine/Engine.kt",
    "content": "package com.github.eltonvs.obd.command.engine\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.bytesToInt\nimport com.github.eltonvs.obd.command.calculatePercentage\n\nprivate const val SINGLE_BYTE = 1\nprivate const val RPM_DIVISOR = 4\nprivate const val MAF_DIVISOR = 100f\nprivate const val SECONDS_PER_HOUR = 3600\nprivate const val SECONDS_PER_MINUTE = 60\nprivate const val TIME_PADDING = 2\nprivate const val PAD_CHAR = '0'\n\nclass SpeedCommand : ObdCommand() {\n    override val tag = \"SPEED\"\n    override val name = \"Vehicle Speed\"\n    override val mode = \"01\"\n    override val pid = \"0D\"\n\n    override val defaultUnit = \"Km/h\"\n    override val handler = { response: ObdRawResponse ->\n        bytesToInt(response.bufferedValue, bytesToProcess = SINGLE_BYTE).toString()\n    }\n}\n\nclass RPMCommand : ObdCommand() {\n    override val tag = \"ENGINE_RPM\"\n    override val name = \"Engine RPM\"\n    override val mode = \"01\"\n    override val pid = \"0C\"\n\n    override val defaultUnit = \"RPM\"\n    override val handler = { response: ObdRawResponse ->\n        (bytesToInt(response.bufferedValue, bytesToProcess = 2) / RPM_DIVISOR).toString()\n    }\n}\n\nclass MassAirFlowCommand : ObdCommand() {\n    override val tag = \"MAF\"\n    override val name = \"Mass Air Flow\"\n    override val mode = \"01\"\n    override val pid = \"10\"\n\n    override val defaultUnit = \"g/s\"\n    override val handler = { response: ObdRawResponse ->\n        val value = bytesToInt(response.bufferedValue) / MAF_DIVISOR\n        \"%.2f\".format(value)\n    }\n}\n\nclass RuntimeCommand : ObdCommand() {\n    override val tag = \"ENGINE_RUNTIME\"\n    override val name = \"Engine Runtime\"\n    override val mode = \"01\"\n    override val pid = \"0F\"\n\n    override val handler = { response: ObdRawResponse -> parseRuntime(response.bufferedValue) }\n\n    private fun parseRuntime(rawValue: IntArray): String {\n        val seconds = bytesToInt(rawValue)\n        val hh = seconds / SECONDS_PER_HOUR\n        val mm = (seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE\n        val ss = seconds % SECONDS_PER_MINUTE\n        return listOf(hh, mm, ss).joinToString(\":\") { it.toString().padStart(TIME_PADDING, PAD_CHAR) }\n    }\n}\n\nclass LoadCommand : ObdCommand() {\n    override val tag = \"ENGINE_LOAD\"\n    override val name = \"Engine Load\"\n    override val mode = \"01\"\n    override val pid = \"04\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.1f\".format(calculatePercentage(response.bufferedValue, bytesToProcess = SINGLE_BYTE))\n    }\n}\n\nclass AbsoluteLoadCommand : ObdCommand() {\n    override val tag = \"ENGINE_ABSOLUTE_LOAD\"\n    override val name = \"Engine Absolute Load\"\n    override val mode = \"01\"\n    override val pid = \"43\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse -> \"%.1f\".format(calculatePercentage(response.bufferedValue)) }\n}\n\nclass ThrottlePositionCommand : ObdCommand() {\n    override val tag = \"THROTTLE_POSITION\"\n    override val name = \"Throttle Position\"\n    override val mode = \"01\"\n    override val pid = \"11\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.1f\".format(calculatePercentage(response.bufferedValue, bytesToProcess = SINGLE_BYTE))\n    }\n}\n\nclass RelativeThrottlePositionCommand : ObdCommand() {\n    override val tag = \"RELATIVE_THROTTLE_POSITION\"\n    override val name = \"Relative Throttle Position\"\n    override val mode = \"01\"\n    override val pid = \"45\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.1f\".format(calculatePercentage(response.bufferedValue, bytesToProcess = SINGLE_BYTE))\n    }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/fuel/Fuel.kt",
    "content": "package com.github.eltonvs.obd.command.fuel\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.bytesToInt\nimport com.github.eltonvs.obd.command.calculatePercentage\n\nprivate const val SINGLE_BYTE = 1\nprivate const val FUEL_CONSUMPTION_FACTOR = 0.05\nprivate const val HUNDRED_PERCENT = 100f\nprivate const val HALF_SCALE = 128f\n\nprivate const val FUEL_CODE_NOT_AVAILABLE = 0x00\nprivate const val FUEL_CODE_GASOLINE = 0x01\nprivate const val FUEL_CODE_METHANOL = 0x02\nprivate const val FUEL_CODE_ETHANOL = 0x03\nprivate const val FUEL_CODE_DIESEL = 0x04\nprivate const val FUEL_CODE_GPL = 0x05\nprivate const val FUEL_CODE_NATURAL_GAS = 0x06\nprivate const val FUEL_CODE_PROPANE = 0x07\nprivate const val FUEL_CODE_ELECTRIC = 0x08\nprivate const val FUEL_CODE_BIODIESEL_GASOLINE = 0x09\nprivate const val FUEL_CODE_BIODIESEL_METHANOL = 0x0A\nprivate const val FUEL_CODE_BIODIESEL_ETHANOL = 0x0B\nprivate const val FUEL_CODE_BIODIESEL_GPL = 0x0C\nprivate const val FUEL_CODE_BIODIESEL_NATURAL_GAS = 0x0D\nprivate const val FUEL_CODE_BIODIESEL_PROPANE = 0x0E\nprivate const val FUEL_CODE_BIODIESEL_ELECTRIC = 0x0F\nprivate const val FUEL_CODE_BIODIESEL_GASOLINE_ELECTRIC = 0x10\nprivate const val FUEL_CODE_HYBRID_GASOLINE = 0x11\nprivate const val FUEL_CODE_HYBRID_ETHANOL = 0x12\nprivate const val FUEL_CODE_HYBRID_DIESEL = 0x13\nprivate const val FUEL_CODE_HYBRID_ELECTRIC = 0x14\nprivate const val FUEL_CODE_HYBRID_MIXED = 0x15\nprivate const val FUEL_CODE_HYBRID_REGENERATIVE = 0x16\n\nclass FuelConsumptionRateCommand : ObdCommand() {\n    override val tag = \"FUEL_CONSUMPTION_RATE\"\n    override val name = \"Fuel Consumption Rate\"\n    override val mode = \"01\"\n    override val pid = \"5E\"\n\n    override val defaultUnit = \"L/h\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.1f\".format(bytesToInt(response.bufferedValue) * FUEL_CONSUMPTION_FACTOR)\n    }\n}\n\nclass FuelTypeCommand : ObdCommand() {\n    override val tag = \"FUEL_TYPE\"\n    override val name = \"Fuel Type\"\n    override val mode = \"01\"\n    override val pid = \"51\"\n\n    override val handler = { response: ObdRawResponse ->\n        getFuelType(bytesToInt(response.bufferedValue, bytesToProcess = SINGLE_BYTE).toInt())\n    }\n\n    private fun getFuelType(code: Int): String = FUEL_TYPE_BY_CODE[code] ?: \"Unknown\"\n\n    private companion object {\n        private val FUEL_TYPE_BY_CODE =\n            mapOf(\n                FUEL_CODE_NOT_AVAILABLE to \"Not Available\",\n                FUEL_CODE_GASOLINE to \"Gasoline\",\n                FUEL_CODE_METHANOL to \"Methanol\",\n                FUEL_CODE_ETHANOL to \"Ethanol\",\n                FUEL_CODE_DIESEL to \"Diesel\",\n                FUEL_CODE_GPL to \"GPL/LGP\",\n                FUEL_CODE_NATURAL_GAS to \"Natural Gas\",\n                FUEL_CODE_PROPANE to \"Propane\",\n                FUEL_CODE_ELECTRIC to \"Electric\",\n                FUEL_CODE_BIODIESEL_GASOLINE to \"Biodiesel + Gasoline\",\n                FUEL_CODE_BIODIESEL_METHANOL to \"Biodiesel + Methanol\",\n                FUEL_CODE_BIODIESEL_ETHANOL to \"Biodiesel + Ethanol\",\n                FUEL_CODE_BIODIESEL_GPL to \"Biodiesel + GPL/LGP\",\n                FUEL_CODE_BIODIESEL_NATURAL_GAS to \"Biodiesel + Natural Gas\",\n                FUEL_CODE_BIODIESEL_PROPANE to \"Biodiesel + Propane\",\n                FUEL_CODE_BIODIESEL_ELECTRIC to \"Biodiesel + Electric\",\n                FUEL_CODE_BIODIESEL_GASOLINE_ELECTRIC to \"Biodiesel + Gasoline/Electric\",\n                FUEL_CODE_HYBRID_GASOLINE to \"Hybrid Gasoline\",\n                FUEL_CODE_HYBRID_ETHANOL to \"Hybrid Ethanol\",\n                FUEL_CODE_HYBRID_DIESEL to \"Hybrid Diesel\",\n                FUEL_CODE_HYBRID_ELECTRIC to \"Hybrid Electric\",\n                FUEL_CODE_HYBRID_MIXED to \"Hybrid Mixed\",\n                FUEL_CODE_HYBRID_REGENERATIVE to \"Hybrid Regenerative\",\n            )\n    }\n}\n\nclass FuelLevelCommand : ObdCommand() {\n    override val tag = \"FUEL_LEVEL\"\n    override val name = \"Fuel Level\"\n    override val mode = \"01\"\n    override val pid = \"2F\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.1f\".format(calculatePercentage(response.bufferedValue, bytesToProcess = SINGLE_BYTE))\n    }\n}\n\nclass EthanolLevelCommand : ObdCommand() {\n    override val tag = \"ETHANOL_LEVEL\"\n    override val name = \"Ethanol Level\"\n    override val mode = \"01\"\n    override val pid = \"52\"\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.1f\".format(calculatePercentage(response.bufferedValue, bytesToProcess = SINGLE_BYTE))\n    }\n}\n\nclass FuelTrimCommand(\n    fuelTrimBank: FuelTrimBank,\n) : ObdCommand() {\n    override val tag = fuelTrimBank.name\n    override val name = fuelTrimBank.displayName\n    override val mode = \"01\"\n    override val pid = fuelTrimBank.pid\n\n    override val defaultUnit = \"%\"\n    override val handler = { response: ObdRawResponse ->\n        val value = bytesToInt(response.bufferedValue, bytesToProcess = SINGLE_BYTE)\n        val normalized = value * (HUNDRED_PERCENT / HALF_SCALE) - HUNDRED_PERCENT\n        \"%.1f\".format(\n            normalized,\n        )\n    }\n\n    enum class FuelTrimBank(\n        val displayName: String,\n        internal val pid: String,\n    ) {\n        SHORT_TERM_BANK_1(\"Short Term Fuel Trim Bank 1\", \"06\"),\n        SHORT_TERM_BANK_2(\"Short Term Fuel Trim Bank 2\", \"07\"),\n        LONG_TERM_BANK_1(\"Long Term Fuel Trim Bank 1\", \"08\"),\n        LONG_TERM_BANK_2(\"Long Term Fuel Trim Bank 2\", \"09\"),\n    }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/fuel/Ratio.kt",
    "content": "package com.github.eltonvs.obd.command.fuel\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.bytesToInt\n\nprivate const val RATIO_BYTES_TO_PROCESS = 2\nprivate const val RATIO_SCALE = 2f\nprivate const val RATIO_DIVISOR = 65_536f\n\nprivate fun calculateFuelAirRatio(rawValue: IntArray): Float =\n    bytesToInt(rawValue, bytesToProcess = RATIO_BYTES_TO_PROCESS) * (RATIO_SCALE / RATIO_DIVISOR)\n\nclass CommandedEquivalenceRatioCommand : ObdCommand() {\n    override val tag = \"COMMANDED_EQUIVALENCE_RATIO\"\n    override val name = \"Fuel-Air Commanded Equivalence Ratio\"\n    override val mode = \"01\"\n    override val pid = \"44\"\n\n    override val defaultUnit = \"F/A\"\n    override val handler = { response: ObdRawResponse -> \"%.2f\".format(calculateFuelAirRatio(response.bufferedValue)) }\n}\n\nclass FuelAirEquivalenceRatioCommand(\n    oxygenSensor: OxygenSensor,\n) : ObdCommand() {\n    override val tag = \"FUEL_AIR_EQUIVALENCE_RATIO_${oxygenSensor.name}\"\n    override val name = \"Fuel-Air Equivalence Ratio - ${oxygenSensor.displayName}\"\n    override val mode = \"01\"\n    override val pid = oxygenSensor.pid\n\n    override val defaultUnit = \"F/A\"\n    override val handler = { response: ObdRawResponse -> \"%.2f\".format(calculateFuelAirRatio(response.bufferedValue)) }\n\n    enum class OxygenSensor(\n        val displayName: String,\n        internal val pid: String,\n    ) {\n        OXYGEN_SENSOR_1(\"Oxygen Sensor 1\", \"34\"),\n        OXYGEN_SENSOR_2(\"Oxygen Sensor 2\", \"35\"),\n        OXYGEN_SENSOR_3(\"Oxygen Sensor 3\", \"36\"),\n        OXYGEN_SENSOR_4(\"Oxygen Sensor 4\", \"37\"),\n        OXYGEN_SENSOR_5(\"Oxygen Sensor 5\", \"38\"),\n        OXYGEN_SENSOR_6(\"Oxygen Sensor 6\", \"39\"),\n        OXYGEN_SENSOR_7(\"Oxygen Sensor 7\", \"3A\"),\n        OXYGEN_SENSOR_8(\"Oxygen Sensor 8\", \"3B\"),\n    }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/pressure/Pressure.kt",
    "content": "package com.github.eltonvs.obd.command.pressure\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.bytesToInt\n\nprivate const val SINGLE_BYTE = 1\nprivate const val FUEL_PRESSURE_MULTIPLIER = 3\nprivate const val FUEL_RAIL_PRESSURE_FACTOR = 0.079\nprivate const val FUEL_RAIL_GAUGE_PRESSURE_MULTIPLIER = 10\n\nclass BarometricPressureCommand : ObdCommand() {\n    override val tag = \"BAROMETRIC_PRESSURE\"\n    override val name = \"Barometric Pressure\"\n    override val mode = \"01\"\n    override val pid = \"33\"\n\n    override val defaultUnit = \"kPa\"\n    override val handler = { response: ObdRawResponse ->\n        bytesToInt(response.bufferedValue, bytesToProcess = SINGLE_BYTE).toString()\n    }\n}\n\nclass IntakeManifoldPressureCommand : ObdCommand() {\n    override val tag = \"INTAKE_MANIFOLD_PRESSURE\"\n    override val name = \"Intake Manifold Pressure\"\n    override val mode = \"01\"\n    override val pid = \"0B\"\n\n    override val defaultUnit = \"kPa\"\n    override val handler = { response: ObdRawResponse ->\n        bytesToInt(response.bufferedValue, bytesToProcess = SINGLE_BYTE).toString()\n    }\n}\n\nclass FuelPressureCommand : ObdCommand() {\n    override val tag = \"FUEL_PRESSURE\"\n    override val name = \"Fuel Pressure\"\n    override val mode = \"01\"\n    override val pid = \"0A\"\n\n    override val defaultUnit = \"kPa\"\n    override val handler = { response: ObdRawResponse ->\n        (bytesToInt(response.bufferedValue, bytesToProcess = SINGLE_BYTE) * FUEL_PRESSURE_MULTIPLIER).toString()\n    }\n}\n\nclass FuelRailPressureCommand : ObdCommand() {\n    override val tag = \"FUEL_RAIL_PRESSURE\"\n    override val name = \"Fuel Rail Pressure\"\n    override val mode = \"01\"\n    override val pid = \"22\"\n\n    override val defaultUnit = \"kPa\"\n    override val handler = { response: ObdRawResponse ->\n        \"%.3f\".format(bytesToInt(response.bufferedValue) * FUEL_RAIL_PRESSURE_FACTOR)\n    }\n}\n\nclass FuelRailGaugePressureCommand : ObdCommand() {\n    override val tag = \"FUEL_RAIL_GAUGE_PRESSURE\"\n    override val name = \"Fuel Rail Gauge Pressure\"\n    override val mode = \"01\"\n    override val pid = \"23\"\n\n    override val defaultUnit = \"kPa\"\n    override val handler = { response: ObdRawResponse ->\n        (bytesToInt(response.bufferedValue) * FUEL_RAIL_GAUGE_PRESSURE_MULTIPLIER).toString()\n    }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/command/temperature/Temperature.kt",
    "content": "package com.github.eltonvs.obd.command.temperature\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.bytesToInt\n\nprivate const val SINGLE_BYTE = 1\nprivate const val CELSIUS_OFFSET = 40f\n\nprivate fun calculateTemperature(rawValue: IntArray): Float =\n    bytesToInt(\n        rawValue,\n        bytesToProcess = SINGLE_BYTE,\n    ) - CELSIUS_OFFSET\n\nclass AirIntakeTemperatureCommand : ObdCommand() {\n    override val tag = \"AIR_INTAKE_TEMPERATURE\"\n    override val name = \"Air Intake Temperature\"\n    override val mode = \"01\"\n    override val pid = \"0F\"\n\n    override val defaultUnit = \"°C\"\n    override val handler = { response: ObdRawResponse -> \"%.1f\".format(calculateTemperature(response.bufferedValue)) }\n}\n\nclass AmbientAirTemperatureCommand : ObdCommand() {\n    override val tag = \"AMBIENT_AIR_TEMPERATURE\"\n    override val name = \"Ambient Air Temperature\"\n    override val mode = \"01\"\n    override val pid = \"46\"\n\n    override val defaultUnit = \"°C\"\n    override val handler = { response: ObdRawResponse -> \"%.1f\".format(calculateTemperature(response.bufferedValue)) }\n}\n\nclass EngineCoolantTemperatureCommand : ObdCommand() {\n    override val tag = \"ENGINE_COOLANT_TEMPERATURE\"\n    override val name = \"Engine Coolant Temperature\"\n    override val mode = \"01\"\n    override val pid = \"05\"\n\n    override val defaultUnit = \"°C\"\n    override val handler = { response: ObdRawResponse -> \"%.1f\".format(calculateTemperature(response.bufferedValue)) }\n}\n\nclass OilTemperatureCommand : ObdCommand() {\n    override val tag = \"ENGINE_OIL_TEMPERATURE\"\n    override val name = \"Engine Oil Temperature\"\n    override val mode = \"01\"\n    override val pid = \"5C\"\n\n    override val defaultUnit = \"°C\"\n    override val handler = { response: ObdRawResponse -> \"%.1f\".format(calculateTemperature(response.bufferedValue)) }\n}\n"
  },
  {
    "path": "src/main/kotlin/com/github/eltonvs/obd/connection/ObdDeviceConnection.kt",
    "content": "package com.github.eltonvs.obd.connection\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport com.github.eltonvs.obd.command.ObdResponse\nimport com.github.eltonvs.obd.command.RegexPatterns.SEARCHING_PATTERN\nimport com.github.eltonvs.obd.command.removeAll\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport java.io.InputStream\nimport java.io.OutputStream\nimport kotlin.system.measureTimeMillis\n\nprivate const val READ_RETRY_DELAY_MS = 500L\n\nclass ObdDeviceConnection\n    @JvmOverloads\n    constructor(\n        private val inputStream: InputStream,\n        private val outputStream: OutputStream,\n        private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,\n    ) {\n        private val runMutex = Mutex()\n        private val responseCache = mutableMapOf<String, ObdRawResponse>()\n\n        suspend fun run(\n            command: ObdCommand,\n            useCache: Boolean = false,\n            delayTime: Long = 0,\n            maxRetries: Int = 5,\n        ): ObdResponse =\n            runMutex.withLock {\n                val cacheKey = cacheKey(command)\n                val obdRawResponse =\n                    if (useCache && responseCache[cacheKey] != null) {\n                        responseCache.getValue(cacheKey)\n                    } else {\n                        runCommand(command, delayTime, maxRetries).also {\n                            if (useCache) {\n                                responseCache[cacheKey] = it\n                            }\n                        }\n                    }\n                command.handleResponse(obdRawResponse)\n            }\n\n        private suspend fun runCommand(\n            command: ObdCommand,\n            delayTime: Long,\n            maxRetries: Int,\n        ): ObdRawResponse {\n            var rawData = \"\"\n            val elapsedTime =\n                measureTimeMillis {\n                    sendCommand(command, delayTime)\n                    rawData = readRawData(maxRetries)\n                }\n            return ObdRawResponse(rawData, elapsedTime)\n        }\n\n        private suspend fun sendCommand(\n            command: ObdCommand,\n            delayTime: Long,\n        ) {\n            withContext(ioDispatcher) {\n                outputStream.write(\"${command.rawCommand}\\r\".toByteArray())\n                outputStream.flush()\n            }\n            if (delayTime > 0) {\n                delay(delayTime)\n            }\n        }\n\n        private suspend fun readRawData(maxRetries: Int): String =\n            withContext(ioDispatcher) {\n                val res = StringBuilder()\n                var retriesCount = 0\n\n                // Read until '>' arrives, stream ends (-1), or retries are exhausted.\n                var isReading = true\n                while (isReading) {\n                    if (inputStream.available() > 0) {\n                        val byteValue = inputStream.read()\n                        if (byteValue == -1) {\n                            isReading = false\n                        } else {\n                            val charValue = byteValue.toChar()\n                            if (charValue == '>') {\n                                isReading = false\n                            } else {\n                                res.append(charValue)\n                            }\n                        }\n                    } else {\n                        if (retriesCount >= maxRetries) {\n                            isReading = false\n                        } else {\n                            retriesCount += 1\n                            delay(READ_RETRY_DELAY_MS)\n                        }\n                    }\n                }\n                removeAll(SEARCHING_PATTERN, res.toString()).trim()\n            }\n\n        private fun cacheKey(command: ObdCommand): String {\n            val classKey = command::class.qualifiedName ?: command.javaClass.name\n            return \"$classKey:${command.rawCommand}\"\n        }\n    }\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/BytesToIntParameterizedTests.kt",
    "content": "package com.github.eltonvs.obd.command\n\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass BytesToIntParameterizedTests(\n    private val bufferedValue: IntArray,\n    private val start: Int,\n    private val bytesToProcess: Int,\n    private val expected: Long,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(intArrayOf(0x0), 0, -1, 0),\n                arrayOf<Any>(intArrayOf(0x1), 0, -1, 1),\n                arrayOf<Any>(intArrayOf(0x1), 0, 1, 1),\n                arrayOf<Any>(intArrayOf(0x10), 0, -1, 16),\n                arrayOf<Any>(intArrayOf(0x11), 0, -1, 17),\n                arrayOf<Any>(intArrayOf(0xFF), 0, -1, 255),\n                arrayOf<Any>(intArrayOf(0xFF, 0xFF), 0, -1, 65535),\n                arrayOf<Any>(intArrayOf(0xFF, 0xFF), 0, 1, 255),\n                arrayOf<Any>(intArrayOf(0xFF, 0x00), 0, -1, 65280),\n                arrayOf<Any>(intArrayOf(0xFF, 0x00), 0, 1, 255),\n                arrayOf<Any>(intArrayOf(0x41, 0x0D, 0x40), 2, -1, 64),\n                arrayOf<Any>(intArrayOf(0x41, 0x0D, 0x40, 0xFF), 2, 1, 64),\n            )\n    }\n\n    @Test\n    fun `test valid results for bytesToInt`() {\n        val result = bytesToInt(bufferedValue, start = start, bytesToProcess = bytesToProcess)\n        assertEquals(expected, result)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/control/AvailableCommands.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\nprivate val AVAILABLE_PIDS_01_TO_20_VALUES =\n    listOf(\n        // Renault Sandero 2014\n        arrayOf<Any>(\n            \"BE3EB811\",\n            intArrayOf(\n                0x1,\n                0x3,\n                0x4,\n                0x5,\n                0x6,\n                0x7,\n                0xb,\n                0xc,\n                0xd,\n                0xe,\n                0xf,\n                0x11,\n                0x13,\n                0x14,\n                0x15,\n                0x1c,\n                0x20,\n            ),\n        ),\n        // Chevrolet Onix 2015\n        arrayOf<Any>(\n            \"BE3FB813\",\n            intArrayOf(\n                0x1,\n                0x3,\n                0x4,\n                0x5,\n                0x6,\n                0x7,\n                0xb,\n                0xc,\n                0xd,\n                0xe,\n                0xf,\n                0x10,\n                0x11,\n                0x13,\n                0x14,\n                0x15,\n                0x1c,\n                0x1f,\n                0x20,\n            ),\n        ),\n        // Toyota Corolla 2015\n        arrayOf<Any>(\n            \"BE1FA813\",\n            intArrayOf(\n                0x1,\n                0x3,\n                0x4,\n                0x5,\n                0x6,\n                0x7,\n                0xc,\n                0xd,\n                0xe,\n                0xf,\n                0x10,\n                0x11,\n                0x13,\n                0x15,\n                0x1c,\n                0x1f,\n                0x20,\n            ),\n        ),\n        // Fiat Siena 2011\n        arrayOf<Any>(\n            \"BE3EB811\",\n            intArrayOf(\n                0x1,\n                0x3,\n                0x4,\n                0x5,\n                0x6,\n                0x7,\n                0xb,\n                0xc,\n                0xd,\n                0xe,\n                0xf,\n                0x11,\n                0x13,\n                0x14,\n                0x15,\n                0x1c,\n                0x20,\n            ),\n        ),\n        // VW Gol 2014\n        arrayOf<Any>(\n            \"BE3EB813\",\n            intArrayOf(\n                0x1,\n                0x3,\n                0x4,\n                0x5,\n                0x6,\n                0x7,\n                0xb,\n                0xc,\n                0xd,\n                0xe,\n                0xf,\n                0x11,\n                0x13,\n                0x14,\n                0x15,\n                0x1c,\n                0x1f,\n                0x20,\n            ),\n        ),\n        // Empty\n        arrayOf<Any>(\"00000000\", intArrayOf()),\n        // Complete\n        arrayOf<Any>(\"FFFFFFFF\", (0x1..0x20).toList().toIntArray()),\n    )\n\n@RunWith(Parameterized::class)\nclass AvailablePIDsCommand01to20ParameterizedTests(\n    private val rawValue: String,\n    private val expected: IntArray,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() = AVAILABLE_PIDS_01_TO_20_VALUES\n    }\n\n    @Test\n    fun `test valid available PIDs 01 to 20 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AvailablePIDsCommand(AvailablePIDsCommand.AvailablePIDsRanges.PIDS_01_TO_20).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(\",\") { \"%02X\".format(it) }, obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass AvailablePIDsCommand21to40ParameterizedTests(\n    private val rawValue: String,\n    private val expected: IntArray,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // Renault Sandero 2014\n                arrayOf<Any>(\"80018001\", intArrayOf(0x21, 0x30, 0x31, 0x40)),\n                // Chevrolet Onix 2015\n                arrayOf<Any>(\n                    \"8007A011\",\n                    intArrayOf(0x21, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x3c, 0x40),\n                ),\n                // Toyota Corolla 2015\n                arrayOf<Any>(\n                    \"9005B015\",\n                    intArrayOf(0x21, 0x24, 0x2e, 0x30, 0x31, 0x33, 0x34, 0x3c, 0x3e, 0x40),\n                ),\n                // Fiat Siena 2011\n                arrayOf<Any>(\"80000000\", intArrayOf(0x21)),\n                // VW Gol 2014\n                arrayOf<Any>(\n                    \"8007A011\",\n                    intArrayOf(0x21, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x3c, 0x40),\n                ),\n                // Empty\n                arrayOf<Any>(\"00000000\", intArrayOf()),\n                // Complete\n                arrayOf<Any>(\"FFFFFFFF\", (0x21..0x40).toList().toIntArray()),\n            )\n    }\n\n    @Test\n    fun `test valid available PIDs 21 to 40 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AvailablePIDsCommand(AvailablePIDsCommand.AvailablePIDsRanges.PIDS_21_TO_40).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(\",\") { \"%02X\".format(it) }, obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass AvailablePIDsCommand41to60ParameterizedTests(\n    private val rawValue: String,\n    private val expected: IntArray,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // Renault Sandero 2014\n                arrayOf<Any>(\"80000000\", intArrayOf(0x41)),\n                // Chevrolet Onix 2015\n                arrayOf<Any>(\n                    \"FED0C000\",\n                    intArrayOf(0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x49, 0x4a, 0x4c, 0x51, 0x52),\n                ),\n                // Toyota Corolla 2015\n                arrayOf<Any>(\n                    \"7ADC8001\",\n                    intArrayOf(0x42, 0x43, 0x44, 0x45, 0x47, 0x49, 0x4a, 0x4c, 0x4d, 0x4e, 0x51, 0x60),\n                ),\n                // VW Gol 2014\n                arrayOf<Any>(\n                    \"FED14400\",\n                    intArrayOf(0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x49, 0x4a, 0x4c, 0x50, 0x52, 0x56),\n                ),\n                // Empty\n                arrayOf<Any>(\"00000000\", intArrayOf()),\n                // Complete\n                arrayOf<Any>(\"FFFFFFFF\", (0x41..0x60).toList().toIntArray()),\n            )\n    }\n\n    @Test\n    fun `test valid available PIDs 41 to 60 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AvailablePIDsCommand(AvailablePIDsCommand.AvailablePIDsRanges.PIDS_41_TO_60).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(\",\") { \"%02X\".format(it) }, obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass AvailablePIDsCommand61to80ParameterizedTests(\n    private val rawValue: String,\n    private val expected: IntArray,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // Toyota Corolla 2015\n                arrayOf<Any>(\"08000000\", intArrayOf(0x65)),\n                // Empty\n                arrayOf<Any>(\"00000000\", intArrayOf()),\n                // Complete\n                arrayOf<Any>(\"FFFFFFFF\", (0x61..0x80).toList().toIntArray()),\n            )\n    }\n\n    @Test\n    fun `test valid available PIDs 61 to 80 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AvailablePIDsCommand(AvailablePIDsCommand.AvailablePIDsRanges.PIDS_61_TO_80).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(\",\") { \"%02X\".format(it) }, obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass AvailablePIDsCommand81toA0ParameterizedTests(\n    private val rawValue: String,\n    private val expected: IntArray,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // Empty\n                arrayOf<Any>(\"00000000\", intArrayOf()),\n                // Complete\n                arrayOf<Any>(\"FFFFFFFF\", (0x81..0xA0).toList().toIntArray()),\n            )\n    }\n\n    @Test\n    fun `test valid available PIDs 81 to A0 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AvailablePIDsCommand(AvailablePIDsCommand.AvailablePIDsRanges.PIDS_81_TO_A0).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(\",\") { \"%02X\".format(it) }, obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/control/Control.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass ModuleVoltageCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"414204E2\", 1.25f),\n                arrayOf<Any>(\"41420000\", 0f),\n                arrayOf<Any>(\"4142FFFF\", 65.535f),\n            )\n    }\n\n    @Test\n    fun `test valid module voltage responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            ModuleVoltageCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.2fV\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass TimingAdvanceCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410E70\", -8f),\n                arrayOf<Any>(\"410E00\", -64f),\n                arrayOf<Any>(\"410EFF\", 63.5f),\n                arrayOf<Any>(\"410EFFFF\", 63.5f),\n            )\n    }\n\n    @Test\n    fun `test valid timing advance responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            TimingAdvanceCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.2f°\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass VINCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: String,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // CAN (ISO-15765) format\n                arrayOf<Any>(\n                    \"0140:4902013933591:425352375248452:4A323938313136\",\n                    \"93YBSR7RHEJ298116\",\n                ),\n                arrayOf<Any>(\n                    \"0140:4902015750301:5A5A5A39395A542:53333932313234\",\n                    \"WP0ZZZ99ZTS392124\",\n                ),\n                // ISO9141-2, KWP2000 Fast and KWP2000 5Kbps (ISO15031) format\n                arrayOf<Any>(\n                    \"490201000000394902023359425349020352375248490204454A323949020538313136\",\n                    \"93YBSR7RHEJ298116\",\n                ),\n                arrayOf<Any>(\n                    \"4902010000005749020250305A5A4902035A39395A4902045453333949020532313234\",\n                    \"WP0ZZZ99ZTS392124\",\n                ),\n                arrayOf<Any>(\n                    \"014 0: 49 02 01 39 42 47 1: 4B 54 34 38 56 30 4A 2: 47 31 34 31 38 30 39\",\n                    \"9BGKT48V0JG141809\",\n                ),\n            )\n    }\n\n    @Test\n    fun `test valid vin responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            VINCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected, obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/control/MIL.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass MILOnCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Boolean,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410100452100\", false),\n                arrayOf<Any>(\"410100000000\", false),\n                arrayOf<Any>(\"41017F000000\", false),\n                arrayOf<Any>(\"41017FFFFFFF\", false),\n                arrayOf<Any>(\"410180000000\", true),\n                arrayOf<Any>(\"410180FFFFFF\", true),\n                arrayOf<Any>(\"4101FFFFFFFF\", true),\n            )\n    }\n\n    @Test\n    fun `test valid MIL on responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            MILOnCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"MIL is ${if (expected) \"ON\" else \"OFF\"}\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass DistanceMILOnCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41210000\", 0),\n                arrayOf<Any>(\"41215C8D\", 23_693),\n                arrayOf<Any>(\"4121FFFF\", 65_535),\n            )\n    }\n\n    @Test\n    fun `test valid distance MIL on responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            DistanceMILOnCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}Km\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass TimeSinceMILOnCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"414D0000\", 0),\n                arrayOf<Any>(\"414D5C8D\", 23_693),\n                arrayOf<Any>(\"414DFFFF\", 65_535),\n            )\n    }\n\n    @Test\n    fun `test valid time since MIL on responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            TimeSinceMILOnCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}min\", obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/control/Monitor.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.Monitors\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\nprivate val completeStatus = SensorStatus(available = true, complete = true)\nprivate val incompleteStatus = SensorStatus(available = true, complete = false)\nprivate val notAvailableCompleteStatus = SensorStatus(available = false, complete = true)\nprivate val notAvailableIncompleteStatus = SensorStatus(available = false, complete = false)\n\nprivate val expected1 =\n    SensorStatusData(\n        milOn = true,\n        dtcCount = 3,\n        isSpark = true,\n        items =\n            Monitors\n                .values()\n                .filter { it.isSparkIgnition ?: true }\n                .map { it to completeStatus }\n                .toMap(),\n    )\nprivate val expected2 =\n    SensorStatusData(\n        milOn = false,\n        dtcCount = 0,\n        isSpark = false,\n        items =\n            mapOf(\n                Monitors.MISFIRE to incompleteStatus,\n                Monitors.FUEL_SYSTEM to notAvailableIncompleteStatus,\n                Monitors.COMPREHENSIVE_COMPONENT to notAvailableIncompleteStatus,\n                Monitors.NMHC_CATALYST to incompleteStatus,\n                Monitors.NOX_SCR_MONITOR to incompleteStatus,\n                Monitors.BOOST_PRESSURE to notAvailableCompleteStatus,\n                Monitors.EXHAUST_GAS_SENSOR to notAvailableCompleteStatus,\n                Monitors.PM_FILTER to notAvailableCompleteStatus,\n                Monitors.EGR_VVT_SYSTEM to notAvailableCompleteStatus,\n            ),\n    )\nprivate val expected3 =\n    SensorStatusData(\n        milOn = false,\n        dtcCount = 0,\n        isSpark = true,\n        items =\n            mapOf(\n                Monitors.CATALYST to completeStatus,\n                Monitors.EGR_SYSTEM to incompleteStatus,\n                Monitors.SECONDARY_AIR_SYSTEM to incompleteStatus,\n                Monitors.COMPREHENSIVE_COMPONENT to completeStatus,\n                Monitors.OXYGEN_SENSOR_HEATER to incompleteStatus,\n                Monitors.HEATED_CATALYST to completeStatus,\n                Monitors.FUEL_SYSTEM to completeStatus,\n                Monitors.OXYGEN_SENSOR to completeStatus,\n                Monitors.MISFIRE to completeStatus,\n                Monitors.EVAPORATIVE_SYSTEM to notAvailableCompleteStatus,\n                Monitors.AC_REFRIGERANT to notAvailableCompleteStatus,\n            ),\n    )\nprivate val expected4 =\n    SensorStatusData(\n        milOn = false,\n        dtcCount = 0,\n        isSpark = true,\n        items =\n            Monitors\n                .values()\n                .filter { it.isSparkIgnition ?: true }\n                .map { it to completeStatus }\n                .toMap(),\n    )\nprivate val expected5 =\n    SensorStatusData(\n        milOn = false,\n        dtcCount = 0,\n        isSpark = false,\n        items =\n            mapOf(\n                Monitors.FUEL_SYSTEM to notAvailableCompleteStatus,\n                Monitors.NMHC_CATALYST to incompleteStatus,\n                Monitors.EXHAUST_GAS_SENSOR to incompleteStatus,\n                Monitors.MISFIRE to notAvailableCompleteStatus,\n                Monitors.PM_FILTER to notAvailableCompleteStatus,\n                Monitors.BOOST_PRESSURE to notAvailableCompleteStatus,\n                Monitors.EGR_VVT_SYSTEM to notAvailableCompleteStatus,\n                Monitors.NOX_SCR_MONITOR to notAvailableCompleteStatus,\n                Monitors.COMPREHENSIVE_COMPONENT to notAvailableIncompleteStatus,\n            ),\n    )\n\n@RunWith(Parameterized::class)\nclass MonitorStatusSinceCodesClearedCommandTests(\n    private val rawValue: String,\n    private val expected: SensorStatusData,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41018307FF00\", expected1),\n                arrayOf<Any>(\"41 01 83 07 FF 00\", expected1),\n                arrayOf<Any>(\"8307FF00\", expected1),\n                arrayOf<Any>(\"410100790303\", expected2),\n                arrayOf<Any>(\"41 01 00 79 03 03\", expected2),\n                arrayOf<Any>(\"00790303\", expected2),\n                arrayOf<Any>(\"41010007EBC8\", expected3),\n                arrayOf<Any>(\"41 01 00 07 EB C8\", expected3),\n                arrayOf<Any>(\"0007EBC8\", expected3),\n            )\n    }\n\n    @Test\n    fun `test valid monitor status since CC responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            MonitorStatusSinceCodesClearedCommand().also {\n                it.handleResponse(rawResponse)\n            }\n        assertEquals(expected, obdResponse.data)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass MonitorStatusCurrentDriveCycleCommandTests(\n    private val rawValue: String,\n    private val expected: SensorStatusData,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41410007FF00\", expected4),\n                arrayOf<Any>(\"41 41 00 07 FF 00\", expected4),\n                arrayOf<Any>(\"0007FF00\", expected4),\n                arrayOf<Any>(\"414100790303\", expected2),\n                arrayOf<Any>(\"41 41 00 79 03 03\", expected2),\n                arrayOf<Any>(\"00790303\", expected2),\n                arrayOf<Any>(\"414100482135\", expected5),\n                arrayOf<Any>(\"41 41 00 48 21 35\", expected5),\n                arrayOf<Any>(\"00482135\", expected5),\n            )\n    }\n\n    @Test\n    fun `test valid monitor status current drive cycle responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            MonitorStatusCurrentDriveCycleCommand().also {\n                it.handleResponse(rawResponse)\n            }\n        assertEquals(expected, obdResponse.data)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/control/TroubleCodes.kt",
    "content": "package com.github.eltonvs.obd.command.control\n\nimport com.github.eltonvs.obd.command.NoDataException\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass DTCNumberCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410100452100\", 0),\n                arrayOf<Any>(\"410100000000\", 0),\n                arrayOf<Any>(\"41017F000000\", 127),\n                arrayOf<Any>(\"410123456789\", 35),\n                arrayOf<Any>(\"41017FFFFFFF\", 127),\n                arrayOf<Any>(\"410180000000\", 0),\n                arrayOf<Any>(\"410180FFFFFF\", 0),\n                arrayOf<Any>(\"410189ABCDEF\", 9),\n                arrayOf<Any>(\"4101FFFFFFFF\", 127),\n            )\n    }\n\n    @Test\n    fun `test valid DTC number responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            DTCNumberCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"$expected codes\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass DistanceSinceCodesClearedCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"4131F967\", 63_847),\n                arrayOf<Any>(\"41310000\", 0),\n                arrayOf<Any>(\"4131FFFF\", 65_535),\n            )\n    }\n\n    @Test\n    fun `test valid distance since codes cleared responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            DistanceSinceCodesClearedCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}Km\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass TimeSinceCodesClearedCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"414E4543\", 17_731),\n                arrayOf<Any>(\"414E0000\", 0),\n                arrayOf<Any>(\"414EFFFF\", 65_535),\n            )\n    }\n\n    @Test\n    fun `test valid time since codes cleared responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            TimeSinceCodesClearedCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}min\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass TroubleCodesCommandsParameterizedTests(\n    private val rawValue: String,\n    private val expected: List<String>,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // Two frames with four dtc\n                arrayOf<Any>(\"4300035104A1AB\\r43F10600000000\", listOf(\"P0003\", \"C1104\", \"B21AB\", \"U3106\")),\n                // One frame with three dtc\n                arrayOf<Any>(\"43010301040105\", listOf(\"P0103\", \"P0104\", \"P0105\")),\n                // One frame with two dtc\n                arrayOf<Any>(\"43010301040000\", listOf(\"P0103\", \"P0104\")),\n                // Two frames with four dtc CAN (ISO-15765) format\n                arrayOf<Any>(\"00A\\r0:430401080118\\r1:011901200000\", listOf(\"P0108\", \"P0118\", \"P0119\", \"P0120\")),\n                // One frame with two dtc CAN (ISO-15765) format\n                arrayOf<Any>(\"430201200121\", listOf(\"P0120\", \"P0121\")),\n                // Empty data\n                arrayOf<Any>(\"4300\", listOf<String>()),\n            )\n    }\n\n    @Test\n    fun `test valid trouble codes responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            TroubleCodesCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(separator = \",\"), obdResponse.formattedValue)\n    }\n}\n\nclass TroubleCodesCommandsTests {\n    @Test(expected = NoDataException::class)\n    fun `test trouble codes no data response`() {\n        val rawResponse = ObdRawResponse(value = \"43NODATA\", elapsedTime = 0)\n        TroubleCodesCommand().run {\n            handleResponse(rawResponse)\n        }\n    }\n}\n\n@RunWith(Parameterized::class)\nclass PendingTroubleCodesCommandsParameterizedTests(\n    private val rawValue: String,\n    private val expected: List<String>,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // Two frames with four dtc\n                arrayOf<Any>(\"4700035104A1AB\\r47F10600000000\", listOf(\"P0003\", \"C1104\", \"B21AB\", \"U3106\")),\n                // One frame with three dtc\n                arrayOf<Any>(\"47010301040105\", listOf(\"P0103\", \"P0104\", \"P0105\")),\n                // One frame with two dtc\n                arrayOf<Any>(\"47010301040000\", listOf(\"P0103\", \"P0104\")),\n                // Two frames with four dtc CAN (ISO-15765) format\n                arrayOf<Any>(\"00A\\r0:470401080118\\r1:011901200000\", listOf(\"P0108\", \"P0118\", \"P0119\", \"P0120\")),\n                // One frame with two dtc CAN (ISO-15765) format\n                arrayOf<Any>(\"470201200121\", listOf(\"P0120\", \"P0121\")),\n                // Empty data\n                arrayOf<Any>(\"4700\", listOf<String>()),\n            )\n    }\n\n    @Test\n    fun `test valid pending trouble codes responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            PendingTroubleCodesCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(separator = \",\"), obdResponse.formattedValue)\n    }\n}\n\nclass PendingTroubleCodesCommandsTests {\n    @Test(expected = NoDataException::class)\n    fun `test pending trouble codes no data response`() {\n        val rawResponse = ObdRawResponse(value = \"47NODATA\", elapsedTime = 0)\n        PendingTroubleCodesCommand().run {\n            handleResponse(rawResponse)\n        }\n    }\n}\n\n@RunWith(Parameterized::class)\nclass PermanentTroubleCodesCommandsParameterizedTests(\n    private val rawValue: String,\n    private val expected: List<String>,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                // Two frames with four dtc\n                arrayOf<Any>(\"4A00035104A1AB\\r4AF10600000000\", listOf(\"P0003\", \"C1104\", \"B21AB\", \"U3106\")),\n                // One frame with three dtc\n                arrayOf<Any>(\"4A010301040105\", listOf(\"P0103\", \"P0104\", \"P0105\")),\n                // One frame with two dtc\n                arrayOf<Any>(\"4A010301040000\", listOf(\"P0103\", \"P0104\")),\n                // Two frames with four dtc CAN (ISO-15765) format\n                arrayOf<Any>(\"00A\\r0:4A0401080118\\r1:011901200000\", listOf(\"P0108\", \"P0118\", \"P0119\", \"P0120\")),\n                // One frame with two dtc CAN (ISO-15765) format\n                arrayOf<Any>(\"4A0201200121\", listOf(\"P0120\", \"P0121\")),\n                // Empty data\n                arrayOf<Any>(\"4A00\", listOf<String>()),\n            )\n    }\n\n    @Test\n    fun `test valid permanent trouble codes responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            PermanentTroubleCodesCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected.joinToString(separator = \",\"), obdResponse.formattedValue)\n    }\n}\n\nclass PermanentTroubleCodesCommandsTests {\n    @Test(expected = NoDataException::class)\n    fun `test permanent trouble codes no data response`() {\n        val rawResponse = ObdRawResponse(value = \"4ANODATA\", elapsedTime = 0)\n        PermanentTroubleCodesCommand().run {\n            handleResponse(rawResponse)\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/egr/Egr.kt",
    "content": "package com.github.eltonvs.obd.command.egr\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass CommandedEgrCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"414545\", 27.1f),\n                arrayOf<Any>(\"414500\", 0f),\n                arrayOf<Any>(\"4145FF\", 100f),\n                arrayOf<Any>(\"4145FFFF\", 100f),\n            )\n    }\n\n    @Test\n    fun `test valid commanded egr responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            CommandedEgrCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass EgrErrorCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410610\", -87.5f),\n                arrayOf<Any>(\"410643\", -47.7f),\n                arrayOf<Any>(\"410680\", 0f),\n                arrayOf<Any>(\"4106C8\", 56.25f),\n                arrayOf<Any>(\"410600\", -100f),\n                arrayOf<Any>(\"4106FF\", 99.2f),\n                arrayOf<Any>(\"4106FFFF\", 99.2f),\n            )\n    }\n\n    @Test\n    fun `test valid egr error responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            EgrErrorCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/engine/Engine.kt",
    "content": "package com.github.eltonvs.obd.command.engine\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass SpeedCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410D15\", 21),\n                arrayOf<Any>(\"410D40\", 64),\n                arrayOf<Any>(\"410D00\", 0),\n                arrayOf<Any>(\"410DFF\", 255),\n                arrayOf<Any>(\"410DFFFF\", 255),\n            )\n    }\n\n    @Test\n    fun `test valid speed responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            SpeedCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}Km/h\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass RPMCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410C200D\", 2051),\n                arrayOf<Any>(\"410C10A4\", 1065),\n                arrayOf<Any>(\"41 0C 10 A4\", 1065),\n                arrayOf<Any>(\"41 0c 10 a4\", 1065),\n                arrayOf<Any>(\"410C10A4410C10C1\", 1065),\n                arrayOf<Any>(\"41 0C 10 A4 41 0C 10 C1\", 1065),\n                arrayOf<Any>(\"410C0E02410C0E02\", 896),\n                arrayOf<Any>(\"41 0C 0E 02  41 0C 0E 02\", 896),\n                arrayOf<Any>(\"410C283C\", 2575),\n                arrayOf<Any>(\"410C0A00\", 640),\n                arrayOf<Any>(\"410C0000\", 0),\n                arrayOf<Any>(\"410CFFFF\", 16_383),\n            )\n    }\n\n    @Test\n    fun `test valid rpm responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            RPMCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}RPM\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass MassAirFlowCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41109511\", 381.61f),\n                arrayOf<Any>(\"41101234\", 46.6f),\n                arrayOf<Any>(\"41100000\", 0f),\n                arrayOf<Any>(\"4110FFFF\", 655.35f),\n            )\n    }\n\n    @Test\n    fun `test valid maf responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            MassAirFlowCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.2fg/s\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass RuntimeCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: String,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"411F4543\", \"04:55:31\"),\n                arrayOf<Any>(\"411F1234\", \"01:17:40\"),\n                arrayOf<Any>(\"411F0000\", \"00:00:00\"),\n                arrayOf<Any>(\"411FFFFF\", \"18:12:15\"),\n            )\n    }\n\n    @Test\n    fun `test valid runtime responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            RuntimeCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected, obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass LoadCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410410\", 6.3f),\n                arrayOf<Any>(\"410400\", 0f),\n                arrayOf<Any>(\"4104FF\", 100f),\n                arrayOf<Any>(\"4104FFFF\", 100f),\n            )\n    }\n\n    @Test\n    fun `test valid engine load responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            LoadCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass AbsoluteLoadCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41434143\", 6551.8f),\n                arrayOf<Any>(\"41431234\", 1827.5f),\n                arrayOf<Any>(\"41430000\", 0f),\n                arrayOf<Any>(\"4143FFFF\", 25_700f),\n            )\n    }\n\n    @Test\n    fun `test valid engine absolute load responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AbsoluteLoadCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass ThrottlePositionCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"411111\", 6.7f),\n                arrayOf<Any>(\"411100\", 0f),\n                arrayOf<Any>(\"4111FF\", 100f),\n                arrayOf<Any>(\"4111FFFF\", 100f),\n            )\n    }\n\n    @Test\n    fun `test valid throttle position responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            ThrottlePositionCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass RelativeThrottlePositionCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"414545\", 27.1f),\n                arrayOf<Any>(\"414500\", 0f),\n                arrayOf<Any>(\"4145FF\", 100f),\n                arrayOf<Any>(\"4145FFFF\", 100f),\n            )\n    }\n\n    @Test\n    fun `test valid relative throttle position responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            RelativeThrottlePositionCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/fuel/Fuel.kt",
    "content": "package com.github.eltonvs.obd.command.fuel\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass FuelConsumptionRateCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"415E10E3\", 216.2f),\n                arrayOf<Any>(\"415E1234\", 233f),\n                arrayOf<Any>(\"415E0000\", 0f),\n                arrayOf<Any>(\"415EFFFF\", 3276.75f),\n            )\n    }\n\n    @Test\n    fun `test valid fuel consumption responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelConsumptionRateCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1fL/h\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass FuelTypeCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: String,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"415100\", \"Not Available\"),\n                arrayOf<Any>(\"415101\", \"Gasoline\"),\n                arrayOf<Any>(\"415102\", \"Methanol\"),\n                arrayOf<Any>(\"415103\", \"Ethanol\"),\n                arrayOf<Any>(\"415104\", \"Diesel\"),\n                arrayOf<Any>(\"415105\", \"GPL/LGP\"),\n                arrayOf<Any>(\"415106\", \"Natural Gas\"),\n                arrayOf<Any>(\"415107\", \"Propane\"),\n                arrayOf<Any>(\"415108\", \"Electric\"),\n                arrayOf<Any>(\"415109\", \"Biodiesel + Gasoline\"),\n                arrayOf<Any>(\"41510A\", \"Biodiesel + Methanol\"),\n                arrayOf<Any>(\"41510B\", \"Biodiesel + Ethanol\"),\n                arrayOf<Any>(\"41510C\", \"Biodiesel + GPL/LGP\"),\n                arrayOf<Any>(\"41510D\", \"Biodiesel + Natural Gas\"),\n                arrayOf<Any>(\"41510E\", \"Biodiesel + Propane\"),\n                arrayOf<Any>(\"41510F\", \"Biodiesel + Electric\"),\n                arrayOf<Any>(\"415110\", \"Biodiesel + Gasoline/Electric\"),\n                arrayOf<Any>(\"415111\", \"Hybrid Gasoline\"),\n                arrayOf<Any>(\"415112\", \"Hybrid Ethanol\"),\n                arrayOf<Any>(\"415113\", \"Hybrid Diesel\"),\n                arrayOf<Any>(\"415114\", \"Hybrid Electric\"),\n                arrayOf<Any>(\"415115\", \"Hybrid Mixed\"),\n                arrayOf<Any>(\"415116\", \"Hybrid Regenerative\"),\n                arrayOf<Any>(\"415116FF\", \"Hybrid Regenerative\"),\n                arrayOf<Any>(\"4151FF\", \"Unknown\"),\n                arrayOf<Any>(\"4151FFFF\", \"Unknown\"),\n            )\n    }\n\n    @Test\n    fun `test valid fuel type responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelTypeCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(expected, obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass GenericFuelLevelCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"412F10\", 6.3f),\n                arrayOf<Any>(\"412FC8\", 78.4f),\n                arrayOf<Any>(\"412F00\", 0f),\n                arrayOf<Any>(\"412FFF\", 100f),\n                arrayOf<Any>(\"412FFFFF\", 100f),\n            )\n    }\n\n    @Test\n    fun `test valid fuel level responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelLevelCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n\n    @Test\n    fun `test valid ethanol level responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            EthanolLevelCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass GenericFuelTrimCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410610\", -87.5f),\n                arrayOf<Any>(\"410643\", -47.7f),\n                arrayOf<Any>(\"410680\", 0f),\n                arrayOf<Any>(\"4106C8\", 56.25f),\n                arrayOf<Any>(\"410600\", -100f),\n                arrayOf<Any>(\"4106FF\", 99.2f),\n                arrayOf<Any>(\"4106FFFF\", 99.2f),\n            )\n    }\n\n    @Test\n    fun `test valid fuel trim short term bank 1 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelTrimCommand(FuelTrimCommand.FuelTrimBank.SHORT_TERM_BANK_1).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n\n    @Test\n    fun `test valid fuel trim short term bank 2 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelTrimCommand(FuelTrimCommand.FuelTrimBank.SHORT_TERM_BANK_2).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n\n    @Test\n    fun `test valid fuel trim long term bank 1 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelTrimCommand(FuelTrimCommand.FuelTrimBank.LONG_TERM_BANK_1).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n\n    @Test\n    fun `test valid fuel trim long term bank 2 responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelTrimCommand(FuelTrimCommand.FuelTrimBank.LONG_TERM_BANK_2).run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f\".format(expected) + '%', obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/fuel/Ratio.kt",
    "content": "package com.github.eltonvs.obd.command.fuel\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass CommandedEquivalenceRatioCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41441234\", 0.14f),\n                arrayOf<Any>(\"41444040\", 0.5f),\n                arrayOf<Any>(\"41448080\", 1f),\n                arrayOf<Any>(\"41440000\", 0f),\n                arrayOf<Any>(\"4144FFFF\", 2f),\n                arrayOf<Any>(\"4144FFFFFFFF\", 2f),\n            )\n    }\n\n    @Test\n    fun `test valid commanded equivalence ratio responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            CommandedEquivalenceRatioCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.2fF/A\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass FuelAirEquivalenceRatioCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41341234\", 0.14f),\n                arrayOf<Any>(\"41344040\", 0.5f),\n                arrayOf<Any>(\"41348080\", 1f),\n                arrayOf<Any>(\"41340000\", 0f),\n                arrayOf<Any>(\"4134FFFF\", 2f),\n                arrayOf<Any>(\"4134FFFFFFFF\", 2f),\n            )\n    }\n\n    @Test\n    fun `test valid fuel air equivalence ratio responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        FuelAirEquivalenceRatioCommand.OxygenSensor.values().forEach {\n            val obdResponse =\n                FuelAirEquivalenceRatioCommand(it).run {\n                    handleResponse(rawResponse)\n                }\n            assertEquals(\"%.2fF/A\".format(expected), obdResponse.formattedValue)\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/pressure/Pressure.kt",
    "content": "package com.github.eltonvs.obd.command.pressure\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass BarometricPressureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"413312\", 18),\n                arrayOf<Any>(\"413340\", 64),\n                arrayOf<Any>(\"413364\", 100),\n                arrayOf<Any>(\"413380\", 128),\n                arrayOf<Any>(\"413300\", 0),\n                arrayOf<Any>(\"4133FF\", 255),\n                arrayOf<Any>(\"4133FFFF\", 255),\n            )\n    }\n\n    @Test\n    fun `test valid barometric pressure responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            BarometricPressureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}kPa\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass IntakeManifoldPressureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410B12\", 18),\n                arrayOf<Any>(\"410B39\", 57),\n                arrayOf<Any>(\"410B40\", 64),\n                arrayOf<Any>(\"410B64\", 100),\n                arrayOf<Any>(\"410B80\", 128),\n                arrayOf<Any>(\"410B00\", 0),\n                arrayOf<Any>(\"410BFF\", 255),\n                arrayOf<Any>(\"410BFFFF\", 255),\n            )\n    }\n\n    @Test\n    fun `test valid intake manifold pressure responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            IntakeManifoldPressureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}kPa\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass FuelPressureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410A12\", 54),\n                arrayOf<Any>(\"410A40\", 192),\n                arrayOf<Any>(\"410A64\", 300),\n                arrayOf<Any>(\"410A80\", 384),\n                arrayOf<Any>(\"410A00\", 0),\n                arrayOf<Any>(\"410AFF\", 765),\n                arrayOf<Any>(\"410AFFFF\", 765),\n            )\n    }\n\n    @Test\n    fun `test valid fuel pressure responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelPressureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}kPa\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass FuelRailPressureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41230000\", 0.000f),\n                arrayOf<Any>(\"410B39\", 4.503f),\n                arrayOf<Any>(\"410B6464\", 2030.300f),\n                arrayOf<Any>(\"4123FFFF\", 5177.265f),\n            )\n    }\n\n    @Test\n    fun `test valid fuel rail pressure responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelRailPressureCommand().run {\n                handleResponse(rawResponse)\n            }\n\n        assertEquals(\"%.3f\".format(expected) + \"kPa\", obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass FuelRailGaugePressureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Int,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"41231234\", 46_600),\n                arrayOf<Any>(\"41234354\", 172_360),\n                arrayOf<Any>(\"412360ED\", 248_130),\n                arrayOf<Any>(\"41238080\", 328_960),\n                arrayOf<Any>(\"41230000\", 0),\n                arrayOf<Any>(\"4123FFFF\", 655_350),\n            )\n    }\n\n    @Test\n    fun `test valid fuel rail gauge pressure responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            FuelRailGaugePressureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"${expected}kPa\", obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/command/temperature/Temperature.kt",
    "content": "package com.github.eltonvs.obd.command.temperature\n\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@RunWith(Parameterized::class)\nclass AirIntakeTemperatureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410F40\", 24f),\n                arrayOf<Any>(\"410F5D\", 53f),\n                arrayOf<Any>(\"410F00\", -40f),\n                arrayOf<Any>(\"410FFF\", 215f),\n                arrayOf<Any>(\"410FFFFF\", 215f),\n            )\n    }\n\n    @Test\n    fun `test valid air intake temperature responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AirIntakeTemperatureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f°C\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass AmbientAirTemperatureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"414640\", 24f),\n                arrayOf<Any>(\"41465D\", 53f),\n                arrayOf<Any>(\"414600\", -40f),\n                arrayOf<Any>(\"4146FF\", 215f),\n                arrayOf<Any>(\"4146FFFF\", 215f),\n            )\n    }\n\n    @Test\n    fun `test valid ambient air intake temperature responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            AmbientAirTemperatureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f°C\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass EngineCoolantTemperatureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"410540\", 24f),\n                arrayOf<Any>(\"41055D\", 53f),\n                arrayOf<Any>(\"410500\", -40f),\n                arrayOf<Any>(\"4105FF\", 215f),\n                arrayOf<Any>(\"4105FFFF\", 215f),\n            )\n    }\n\n    @Test\n    fun `test valid engine coolant temperature responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            EngineCoolantTemperatureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f°C\".format(expected), obdResponse.formattedValue)\n    }\n}\n\n@RunWith(Parameterized::class)\nclass OilTemperatureCommandParameterizedTests(\n    private val rawValue: String,\n    private val expected: Float,\n) {\n    companion object {\n        @JvmStatic\n        @Parameterized.Parameters\n        fun values() =\n            listOf(\n                arrayOf<Any>(\"415C40\", 24f),\n                arrayOf<Any>(\"415C5D\", 53f),\n                arrayOf<Any>(\"415C00\", -40f),\n                arrayOf<Any>(\"415CFF\", 215f),\n                arrayOf<Any>(\"415CFFFF\", 215f),\n            )\n    }\n\n    @Test\n    fun `test valid oil temperature responses handler`() {\n        val rawResponse = ObdRawResponse(value = rawValue, elapsedTime = 0)\n        val obdResponse =\n            OilTemperatureCommand().run {\n                handleResponse(rawResponse)\n            }\n        assertEquals(\"%.1f°C\".format(expected), obdResponse.formattedValue)\n    }\n}\n"
  },
  {
    "path": "src/test/kotlin/com/github/eltonvs/obd/connection/ObdDeviceConnectionTest.kt",
    "content": "package com.github.eltonvs.obd.connection\n\nimport com.github.eltonvs.obd.command.ObdCommand\nimport com.github.eltonvs.obd.command.ObdRawResponse\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CompletableDeferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.withTimeout\nimport kotlinx.coroutines.yield\nimport java.io.ByteArrayOutputStream\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.util.ArrayDeque\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertFailsWith\nimport kotlin.test.assertTrue\n\nclass ObdDeviceConnectionTest {\n    @Test\n    fun `runs one command at a time when invoked concurrently`() =\n        runBlocking {\n            val input = ScriptedInputStream()\n            val output =\n                ScriptedOutputStream(\n                    input = input,\n                    responses =\n                        mapOf(\n                            \"01 0D\" to ResponsePlan(payload = \"410D40>\", autoEnqueue = false),\n                            \"01 0C\" to ResponsePlan(payload = \"410C1AF8>\"),\n                        ),\n                )\n\n            val connection = ObdDeviceConnection(input, output, Dispatchers.Default)\n            val speedCommand = TestObdCommand(tag = \"SPEED\", pid = \"0D\")\n            val rpmCommand = TestObdCommand(tag = \"RPM\", pid = \"0C\")\n\n            val first = async { connection.run(speedCommand) }\n            withTimeout(1_000) {\n                while (output.writes.isEmpty()) {\n                    delay(10)\n                }\n            }\n\n            val secondStarted = CompletableDeferred<Unit>()\n            val second =\n                async {\n                    secondStarted.complete(Unit)\n                    connection.run(rpmCommand)\n                }\n            secondStarted.await()\n            repeat(20) { yield() }\n            assertEquals(listOf(\"01 0D\"), output.writes)\n\n            input.enqueue(\"410D40>\")\n            first.await()\n            second.await()\n            assertEquals(listOf(\"01 0D\", \"01 0C\"), output.writes)\n        }\n\n    @Test\n    fun `uses cache safely when called concurrently`() =\n        runBlocking {\n            val input = ScriptedInputStream()\n            val output =\n                ScriptedOutputStream(\n                    input = input,\n                    responses = mapOf(\"01 05\" to ResponsePlan(payload = \"41057B>\")),\n                )\n\n            val connection = ObdDeviceConnection(input, output, Dispatchers.Default)\n            val commandA = TestObdCommand(tag = \"COOLANT_TEMP\", pid = \"05\")\n            val commandB = TestObdCommand(tag = \"COOLANT_TEMP\", pid = \"05\")\n\n            val first = async { connection.run(commandA, useCache = true) }\n            val second = async { connection.run(commandB, useCache = true) }\n\n            assertEquals(\"41057B\", first.await().value)\n            assertEquals(\"41057B\", second.await().value)\n            assertEquals(1, output.writes.size)\n        }\n\n    @Test\n    fun `handles EOF while reading response`() =\n        runBlocking {\n            val input = DisconnectAfterPayloadInputStream(payload = \"410D40\")\n            val output = ByteArrayOutputStream()\n            val connection = ObdDeviceConnection(input, output, Dispatchers.Default)\n            val command = TestObdCommand(tag = \"SPEED\", pid = \"0D\")\n\n            val response = connection.run(command)\n\n            assertEquals(\"410D40\", response.value)\n            assertEquals(\"410D40\", response.rawResponse.value)\n        }\n\n    @Test\n    fun `propagates cancellation while waiting for data`() {\n        runBlocking {\n            val input = IdleInputStream()\n            val output = ByteArrayOutputStream()\n            val connection = ObdDeviceConnection(input, output, Dispatchers.Default)\n            val command = TestObdCommand(tag = \"SPEED\", pid = \"0D\")\n\n            withTimeout(2_000) {\n                val runningCommand = async { connection.run(command, maxRetries = 1_000) }\n                delay(100)\n                runningCommand.cancel()\n\n                assertFailsWith<CancellationException> { runningCommand.await() }\n            }\n        }\n    }\n\n    @Test\n    fun `returns quickly when max retries is zero and no data is available`() =\n        runBlocking {\n            val input = IdleInputStream()\n            val output = ByteArrayOutputStream()\n            val connection = ObdDeviceConnection(input, output, Dispatchers.Default)\n            val command = TestObdCommand(tag = \"SPEED\", pid = \"0D\")\n\n            withTimeout(400) {\n                val response = connection.run(command, maxRetries = 0)\n                assertEquals(\"\", response.value)\n            }\n        }\n}\n\nprivate class TestObdCommand(\n    override val tag: String,\n    override val pid: String,\n    override val mode: String = \"01\",\n) : ObdCommand() {\n    override val name: String = tag\n    override val skipDigitCheck: Boolean = true\n    override val handler: (ObdRawResponse) -> String = { it.processedValue }\n}\n\nprivate data class ResponsePlan(\n    val payload: String,\n    val autoEnqueue: Boolean = true,\n)\n\nprivate class ScriptedInputStream : InputStream() {\n    private val bytes = ArrayDeque<Int>()\n\n    @Synchronized\n    fun enqueue(payload: String) {\n        payload.toByteArray().forEach { byte ->\n            bytes.addLast(byte.toInt() and 0xFF)\n        }\n    }\n\n    @Synchronized\n    override fun available(): Int = bytes.size\n\n    @Synchronized\n    override fun read(): Int = if (bytes.isNotEmpty()) bytes.removeFirst() else -1\n}\n\nprivate class ScriptedOutputStream(\n    private val input: ScriptedInputStream,\n    private val responses: Map<String, ResponsePlan>,\n) : OutputStream() {\n    val writes = mutableListOf<String>()\n\n    override fun write(b: Int): Unit = throw UnsupportedOperationException(\"write(Int) is not used in these tests\")\n\n    @Synchronized\n    override fun write(\n        b: ByteArray,\n        off: Int,\n        len: Int,\n    ) {\n        val rawCommand = String(b, off, len).trim()\n        writes.add(rawCommand)\n\n        val plan =\n            responses[rawCommand]\n                ?: throw IllegalArgumentException(\"Missing scripted response for command [$rawCommand]\")\n\n        if (plan.autoEnqueue) {\n            input.enqueue(plan.payload)\n        }\n    }\n}\n\nprivate class DisconnectAfterPayloadInputStream(\n    payload: String,\n) : InputStream() {\n    private val bytes = payload.toByteArray()\n    private var index = 0\n    private var eofReturned = false\n\n    override fun available(): Int = if (index < bytes.size || !eofReturned) 1 else 0\n\n    override fun read(): Int =\n        if (index < bytes.size) {\n            bytes[index++].toInt() and 0xFF\n        } else {\n            eofReturned = true\n            -1\n        }\n}\n\nprivate class IdleInputStream : InputStream() {\n    override fun available(): Int = 0\n\n    override fun read(): Int = -1\n}\n"
  }
]