Vehicle-Diagnostic-Assistant / mcp_server_detail.md
castlebbs's picture
Add mcp_server_detail.md
f00302f

MCP Server on embedded device

In order for an AI agent to diagnose a vehicle, we needed to create a MCP server, which can send commands via the vehicle's OBD-II port. On this page, @castlebbs explains how he developed a custom firmware running a MCP server. The firmware was installed on an inexpensive OBD-II scanner (dongle).

Disclaimer: We did this just for fun, just for the technical challenge and for the learning opportunity. We don't recommend letting an AI agent control your car directly without more controls.

Creation of a MCP Server as a custom OBD-II dongle firmware

I looked at the inexpensive OBD-II dongle for sale online. They generally support Bluetooth or Wifi. These are dongles, which you connect to your vehicle's OBD port. You generally use a mobile app connecting to the dongle (via Bluetooth or Wifi) to run diagnostics.

I initially thought of creating a MCP Server to act as a bridge: basically receiving MCP tool calls, and in turn calling the OBD dongles over WiFi or Bluetooth over their existing protocols.

As I was researching a bit more the hardware of these OBD dongles, I realized it would be way cooler to actually run the MCP server in the dongle's hardware themselves. That meant creating a new firmware.

The devices I was looking at have two MCUs:

  1. MCU to communicate with the OBD-II port of the vehicle (ELM327)
  2. MCU to manage Wi-Fi or Bluetooth connection with client applications.

I thought that if I could replace the firmware of this second MCU, I would be able to directly implement the MCP protocol and have the AI agent directly communicating with the dongle without any intermediary.

Disclaimer: To be clear, this is just a fun project, I don't think reprogramming your OBD-II dongle is the best approach to achieve this. I like embedded programming and I thought it was an interesting challenge. You will surely lose your warranty on your dongle, and there is a high chance you will damage and/or brick your device, or worse, your car!

I identified and ordered a "OBD II Car Diagnostic Tool Auto Scanner Code Reader WiFi" on Amazon. There were actually pictures of the electronics inside and I identified that the Wi-Fi MCU was a ESP8266. This was great as it is easy to reprogram. While I waited to receive my dongle, I started to work on a firmware for the ESP8266, we didn't have much time with the hackathon.

A few days later, my dongle was delivered. It was a big surprise because the Wi-Fi MCU was not the ESP8266 I expected. It was a different processor: WinnerMicro W600-B800. I never heard about it. I didn't even know if my project was achievable anymore, and obviously the Hackathon deadline was getting closer.

There is little information about the W600-B800, but there is a SDK, and there are some PDF documents from the manufacturer website. And kudos to WinnerMicro, their SDK just worked. The instructions to set up the toolchain worked, and while the documentation is limited, I was already familiar with FreeRTOS/lwIP which their SDK is using. The SDK includes a few code examples, which were in fact sufficient to know what I needed to do. Claude Code also helped in understanding the SDK. Coding assistants are a real game changer for embedded programming, to help understanding a new SDK. So I'd say the experience with programming the W600 was smooth sailing.

Really importantly, I found this page. The author already identified the test pads, which could be used to upload the firmware. He also successfully uploaded a different firmware. At this point I was more confident I'd be able to pull this out.

I was able to upload my firmwares by connecting to the PCB test pads:

  • Programmer UART connected to W600 UART0, 115200 8N1 via needles on test pads.
  • With minicom, switch to firmware upload mode during SECBOOT
  • In upload mode, upload the firmware with the xmodem protocol
  • WinnerMicro Instructions
Probes on test pads

1. Minimal firmware to support over-the-air (OTA) updates

Placing probes on the test pads to upload firmware or accessing logs was not ideal. My first goal was to create a minimal firmware, which would:

  1. Connect the device to my Wi-Fi network (STA mode + DHCP).
  2. Automatically check if a firmware was available at a specific url, and if available, proceed to OTA (Over The Air update)
  3. Send logs via UDP syslog

#1 and #2 were straightforward. There are demos doing just that, available in the SDK. I have implemented this at the beginning of App/main.c

For #3, there is no support for network logging in the SDK. Therefore I modified the existing logging library to add support for non-blocking UDP syslog. The changes were made in: Src/App/easylogger/port/elog_port.c and Include/wm_log.h At this point, I was able to very easily update the firmware on the device (OTA) and remotely monitor what the device was doing. That was great progress.

2. ELM327 Driver

The next step was to create a ELM327 driver, for the W600 to communicate with the ELM327 chip to send instructions (AT commands, OBD-II commands) and retrieve responses. I also wanted a simulation mode for when the device is powered, but not connected to a vehicle. In this case, the AT commands would be sent to the hardware, but the OBD-II commands would be answered by the simulator.

I need to say that before I erased the original firmware, I probed the serial lines between the W600 and the ELM327 and recorded the communication. This was in line with the ELM327 documentation. You don’t want to be in a situation where you don’t know how your MCUs or peripherals communicate and you don’t have the original firmware.

UART1 from W600 connected to EUSART1(First UART) on the ELM327. 38400 8N1.

ELM327 driver: Src/App/elm327/elm327.c and

ELM327 API

Function Purpose Key Return Values
elm327_init() Initialize UART1 @ 38400 baud WM_SUCCESS / WM_FAILED
elm327_send_command(cmd, resp, len, timeout) Send OBD-II command >0 bytes, -1 not init, -2 invalid params, -3 UART fail, -4 timeout
elm327_deinit() Cleanup resources void

Simulation API (optional)

Available when compiled with TLS_CONFIG_ELM327_SIMULATION.

Function Purpose Returns
elm327_set_simulation_mode(enable) Enable/disable simulation mode void
elm327_is_simulation_enabled() Check if simulation is active true / false
elm327_set_engine_state(running) Set simulated engine state void
Simulation Notes:
  • When enabled, OBD-II commands return simulated responses
  • AT commands always go to real hardware
  • Engine state affects simulated RPM, temperature, and other dynamic values

Configuration

  • UART: UART1, 38400 baud, 8N1, no flow control
  • Max response: 512 bytes (ELM327_MAX_RESPONSE_LEN)
  • Default timeout: 2000ms (ELM327_DEFAULT_TIMEOUT)

The SDK defines two UART operation modes (interrupt and polling) in its API, but the actual implementation only supports interrupt mode. The polling mode enum exists but the corresponding code path is commented out (see Platform/Drivers/uart/wm_uart.c:376). Interrupts are always enabled regardless of the mode parameter. For the retrieval of ELM327 responses, I rely on the interrupt-driven RX callback, and the end of the response is signaled via a FreeRTOS semaphore.

3. MCP Server Architecture

The MCP server is the main part of this project. It mostly complies with the MCP HTTP Streamable transport (version 2025-06-18): https://modelcontextprotocol.io/specification/2025-06-18/basic/transports

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                        MCP Client                                    β”‚
β”‚                      (AI agent, Gradio, Claude Code, etc.)                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚
                                         β”‚ HTTP POST /mcp
                                         β”‚ (JSON-RPC over HTTP)
                                         β”‚
                                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                     W600 WiFi SoC                                    β”‚
β”‚                                                                                      β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚ β”‚                            MCP Server Orchestrator                              β”‚  β”‚
β”‚ β”‚                                                                                 β”‚  β”‚
β”‚ β”‚  β€’ TCP connection management (lwIP)                                             β”‚  β”‚
β”‚ β”‚  β€’ Request buffering & reassembly                                               β”‚  β”‚
β”‚ β”‚  β€’ Coordinate HTTP, JSON-RPC, and method layers                                 β”‚  β”‚
β”‚ β”‚  β€’ Response lifecycle management                                                β”‚  β”‚
β”‚ β”‚                                                                                 β”‚  β”‚
β”‚ β”‚                            (mcp_server.c - Port 80)                             β”‚  β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚               β”‚                            β”‚                        β”‚                β”‚
β”‚               β”‚                            β”‚                        β”‚                β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚                      β”‚   β”‚                        β”‚   β”‚                      β”‚   β”‚
β”‚   β”‚   HTTP Protocol      β”‚   β”‚    JSON-RPC Parser/    β”‚   β”‚   MCP Method Router  β”‚   β”‚
β”‚   β”‚      Handler         β”‚   β”‚        Builder         β”‚   β”‚                      β”‚   β”‚
β”‚   β”‚                      β”‚   β”‚                        β”‚   β”‚                      β”‚   β”‚
β”‚   β”‚  β€’ Parse HTTP        β”‚   β”‚  β€’ Parse JSON-RPC      β”‚   β”‚  β€’ initialize        β”‚   β”‚
β”‚   β”‚    requests          β”‚   β”‚    messages            β”‚   β”‚  β€’ tools/list        β”‚   β”‚
β”‚   β”‚  β€’ Build HTTP        β”‚   β”‚  β€’ Build success       β”‚   β”‚  β€’ tools/call        β”‚   β”‚
β”‚   β”‚    responses         β”‚   β”‚    responses           β”‚   β”‚                      β”‚   β”‚
β”‚   β”‚  β€’ Validate          β”‚   β”‚  β€’ Build error         β”‚   β”‚  Dispatches to       β”‚   β”‚
β”‚   β”‚    headers           β”‚   β”‚    responses           β”‚   β”‚  tool handlers       β”‚   β”‚
β”‚   β”‚  β€’ Status codes      β”‚   β”‚  β€’ JSON-RPC 2.0        β”‚   β”‚                      β”‚   β”‚
β”‚   β”‚                      β”‚   β”‚    compliance          β”‚   β”‚  (mcp_methods.c)     β”‚   β”‚
β”‚   β”‚  (mcp_http.c)        β”‚   β”‚                        β”‚   β”‚                      β”‚   β”‚
β”‚   β”‚                      β”‚   β”‚  (mcp_jsonrpc.c)       β”‚   β”‚                      β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                     β”‚                β”‚
β”‚                                                                     β”‚                β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚   β”‚                              Tool Handlers                                    β”‚  β”‚
β”‚   β”‚                                                                               β”‚  β”‚
β”‚   β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚  β”‚
β”‚   β”‚    β”‚                               β”‚     β”‚                                β”‚   β”‚  β”‚
β”‚   β”‚    β”‚         status                β”‚     β”‚      send_elm327_command       β”‚   β”‚  β”‚
β”‚   β”‚    β”‚                               β”‚     β”‚                                β”‚   β”‚  β”‚
β”‚   β”‚    β”‚  β€’ IP address                 β”‚     β”‚  β€’ Send AT commands            β”‚   β”‚  β”‚
β”‚   β”‚    β”‚  β€’ Network info               β”‚     β”‚  β€’ Send OBD-II PIDs            β”‚   β”‚  β”‚
β”‚   β”‚    β”‚  β€’ System uptime              β”‚     β”‚  β€’ Read responses              β”‚   β”‚  β”‚
β”‚   β”‚    β”‚  β€’ Memory usage               β”‚     β”‚  β€’ Format results              β”‚   β”‚  β”‚
β”‚   β”‚    β”‚                               β”‚     β”‚                                β”‚   β”‚  β”‚
β”‚   β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚  β”‚
β”‚   β”‚                                                        β”‚                      β”‚  β”‚
β”‚   β”‚                           (mcp_tools.c)                β”‚                      β”‚  β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                            β”‚                         β”‚
β”‚                                                            β”‚                         β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚   β”‚                                ELM327 Driver                                  β”‚  β”‚
β”‚   β”‚                                                                               β”‚  β”‚
β”‚   β”‚  β€’ UART1 communication @ 38400 baud                                           β”‚  β”‚
β”‚   β”‚  β€’ AT command protocol                                                        β”‚  β”‚
β”‚   β”‚  β€’ OBD-II PID queries                                                         β”‚  β”‚
β”‚   β”‚  β€’ 2-second default timeout                                                   β”‚  β”‚
β”‚   β”‚  β€’ Response parsing                                                           β”‚  β”‚
β”‚   β”‚                                                                               β”‚  β”‚
β”‚   β”‚                              (elm327.c)                                       β”‚  β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                             β”‚                                        β”‚
β”‚                                             β–Ό                                        β”‚
β”‚                                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                               β”‚
β”‚                                   β”‚   ELM327 Chip    β”‚                               β”‚
β”‚                                   β”‚   (UART1)        β”‚                               β”‚
β”‚                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                             β”‚
                                             β”‚
                                             β–Ό
                                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                   β”‚  Vehicle OBD-II  β”‚
                                   β”‚      Port        β”‚
                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The firmware implements a complete MCP server stack directly on the W600 microcontroller, enabling AI agents to communicate with vehicle diagnostics hardware without any intermediary services or bridge software.

MCP Server Orchestrator

Implementation: mcp_server.c

The orchestrator is the foundation layer that manages all network operations using lwIP's callback-based TCP API. It listens on port 80, handles incoming connections, buffers and reassembles HTTP requests that may arrive in multiple TCP packets, and coordinates the request-response lifecycle across the protocol stack. The server operates through lwIP callbacks (accept, receive, error) invoked by the TCP/IP thread, allowing it to efficiently manage multiple concurrent client connections without requiring a dedicated task.

The orchestrator directly invokes three parallel layers to handle different aspects of the protocol:

  • HTTP Protocol Handler (mcp_http.c) for HTTP parsing and response building
  • JSON-RPC Parser/Builder (mcp_jsonrpc.c) for JSON-RPC message handling
  • MCP Method Router (mcp_methods.c) for method dispatch

This design provides clear separation of concerns while keeping the orchestration logic centralized in mcp_server.c.

HTTP Protocol Handler

Implementation: mcp_http.c

This module implements HTTP protocol parsing and response generation. It extracts HTTP methods, paths, headers, and body content from raw TCP data, validates required headers (like Content-Type and Content-Length), and constructs properly formatted HTTP responses with appropriate status codes and headers. The handler is designed to work within the memory constraints of an embedded system and is called directly by the orchestrator.

JSON-RPC Parser/Builder

Implementation: mcp_jsonrpc.c

The MCP protocol uses JSON-RPC 2.0 as its message format. This component provides parsing and building functions for JSON-RPC messages. It parses incoming JSON-RPC requests, validates the structure (checking for required fields like "jsonrpc", "method", and "id"), and packages responses back into valid JSON-RPC format with proper error handling. Note that this module only handles message formatting - the actual method routing is performed by the MCP Method Router.

MCP Method Router

Implementation: mcp_methods.c

This module implements the core MCP protocol methods defined in the specification. It handles the "initialize" handshake that establishes the MCP session, responds to "tools/list" requests with available diagnostic tools, and processes "tools/call" invocations that execute specific diagnostic operations. The router is called directly by the orchestrator and maintains protocol compliance while adapting the MCP specification to the embedded environment.

Tool Handlers

Implementation: mcp_tools.c

The actual diagnostic capabilities are implemented as MCP tools. The "status" tool reports device health information including IP address, network connectivity, uptime, and available memory. The "send_elm327_command" tool is the primary diagnostic interface, allowing AI agents to send both AT commands (for ELM327 configuration) and OBD-II PIDs (for vehicle data) and receive responses. These tools translate high-level MCP tool calls into low-level hardware operations.

ELM327 Driver Layer

Implementation: elm327.c

This driver communicates with the ELM327 chip over UART1 at 38400 baud, implementing the ELM327 command-response protocol with proper timeout handling and response parsing. It includes an optional simulation mode (enabled at compile time) that generates realistic vehicle responses when the device is powered but not connected to a vehicle, facilitating development and testing without requiring an actual car.

Hardware Interface

At the bottom of the stack, the firmware interfaces with the physical ELM327 chip, which in turn communicates with the vehicle's OBD-II port using standard automotive protocols (CAN, J1850, ISO 9141, etc.). This hardware layer translates between the UART-based ELM327 protocol and the vehicle's native diagnostic bus.