A real-time telemetry decoding engine and marine helm web dashboard for Yamaha Outboard Engines (calibrated for Yamaha F150 ECU 63P-8591A-01 / 63P-01) connected via a 12V ISO 9141 K-Line USB diagnostic adapter to a Raspberry Pi or PC.
| OBD2 Cable Pin | YDS 3-Pin Connector Wire | Description | Notes |
|---|---|---|---|
| Pin 16 | Red Wire | +12V Power | Switched +12V DC from Engine Harness |
| Pin 4 / Pin 5 | Black Wire | Engine / Battery Ground | Chassis / Battery Negative Ground |
| Pin 7 (K-Line) | Data Wire (Yellow/White) | ISO 9141 K-Line Data Signal | ~4V–12V TTL Data Line (Do NOT use Pin 15 L-Line) |
- Yamaha Outboard Engine: F150 / F115 / F200 / F225 (ECU
63P-8591A-01/63P-01). - USB K-Line Adapter: 12V ISO 9141-2 K-Line diagnostic cable (
/dev/ttyUSB0). - USB GPS / GNSS Receiver: u-blox 7 USB GNSS Receiver (Vendor ID
1546, Product ID01a7,cdc_acmdriver, device/dev/ttyACM0). Parses NMEA$GPRMCand$GPGGAsentences to provide real-time Vessel Speed (KTS), Heading/Course (°N), Satellite Fix State, Satellite Count, calculates Live Fuel Economy (L/NM= Fuel Rate L/h / Speed KTS), and automatically synchronizes host system clock if local time drifts by more than 5.0 seconds from satellite GPS UTC.
Important
Ignition Key Requirement: The boat ignition key switch MUST BE ON for the ECU to power up and respond to K-Line requests.
Update packages and install Python 3, virtual environment tools, and udev:
sudo apt update && sudo apt install -y python3-pip python3-venv git udevClone or navigate to the project directory, create a virtual environment, and install required modules:
cd /home/pi/yamaha-data-logger
python3 -m venv venv
source venv/bin/activate
pip install pyserial fastapi uvicorn websocketsAllow the non-root user to access serial devices:
sudo usermod -a -G dialout $USER(Log out and log back in or reboot for group changes to take effect).
To assign a fixed device symlink (e.g. /dev/ttyUSB-yds) regardless of USB port plugging order:
-
Identify USB serial chipset vendor & product ID:
lsusb
(Common chipsets: FTDI
0403:6001or CH3401a86:7523) -
Create a
udevrule file:sudo nano /etc/udev/rules.d/99-yamaha-yds.rules
-
Add the rule lines (adjust
idVendor/idProductif necessary):# Yamaha YDS K-Line Cable (/dev/ttyUSB0 -> /dev/ttyUSB-yds) SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="ttyUSB-yds", GROUP="dialout", MODE="0666" # u-blox 7 GPS Receiver (/dev/ttyACM0 -> /dev/ttyACM-gps) SUBSYSTEM=="tty", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="ttyACM-gps", GROUP="dialout", MODE="0666" -
Reload
udevrules:sudo udevadm control --reload-rules && sudo udevadm trigger
Follow these steps to verify hardware connection, K-Line communication, serial handshakes, and telemetry parsing.
Verify local cable loopback behavior before plugging into the engine:
python3 tests/test_yds_hardware.py --port /dev/ttyUSB0- Expected Outcome: Determines whether the adapter echoes bytes locally (cable internal loopback) or only when connected to an active K-Line bus.
Run an exhaustive bus scan to probe wakeup handshakes and custom query opcodes:
python3 tests/scan_yamaha_yds.py --port /dev/ttyUSB0Test the calibrated telemetry decoder directly in the terminal to verify live parameter reading:
python3 app/yds_reader.py --port /dev/ttyUSB0 --baud 9600- Expected Outcome: Displays real-time RPM, Engine Operating Hours, TPS %, MAP Pressure, Oil Pressure, and Temperatures directly in the console output.
Launch the FastAPI WebSocket server and dashboard interface:
python3 app/server.py --serial-port /dev/ttyUSB0 --gps-port /dev/ttyACM0 --baud 9600 --web-port 8000- Open a web browser to
http://localhost:8000orhttp://<RPI-IP>:8000. - Verify REST status API:
http://localhost:8000/api/status
If copying repository files directly to a remote Raspberry Pi via scp (without git clone), ensure all core Python modules and static assets are included:
scp -r app/ tools/ requirements.txt admin@<PI-IP>:/path/to/yamaha-data-logger/You can run both the reader CLI and the web server in mock mode to test the UI or develop software without connecting to the engine:
- CLI Reader Mock:
python3 app/yds_reader.py --mock
- Web Server Mock:
python3 app/server.py --mock --web-port 8000
To run the telemetry server automatically when the system boots:
-
Create service file:
sudo nano /etc/systemd/system/yamaha-telemetry.service
-
Add configuration:
[Unit] Description=Yamaha YDS Real-Time Telemetry Web Server After=network.target [Service] Type=simple User=pi WorkingDirectory=/home/pi/yamaha-data-logger ExecStart=/home/pi/yamaha-data-logger/venv/bin/python3 /home/pi/yamaha-data-logger/app/server.py --serial-port /dev/ttyUSB0 --gps-port /dev/ttyACM0 --web-port 8000 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target
-
Enable and start service:
sudo systemctl daemon-reload sudo systemctl enable yamaha-telemetry.service sudo systemctl start yamaha-telemetry.service -
Monitor status & logs:
sudo systemctl status yamaha-telemetry.service journalctl -u yamaha-telemetry.service -f
Connect phones or tablets to the boat Wi-Fi or Raspberry Pi hotspot and navigate to:
http://<RASPBERRY-PI-IP>:8000
The server broadcasts WebSocket telemetry to multiple concurrent client devices with ultra-low latency.
To launch Chromium automatically in full-screen 800x480 kiosk mode on boot:
-
Install dependencies:
sudo apt install -y chromium-browser unclutter
-
Add autostart entries in
~/.config/lxsession/LXDE-pi/autostart(or Wayland/labwc config):@xset s off @xset -dpms @xset s noblank @unclutter -idle 0.5 -root @chromium-browser --noerrdialogs --disable-infobars --kiosk --window-size=800,480 --window-position=0,0 http://localhost:8000
If diagnostic scripts show transmitted bytes echoed back but no response payload from the ECU:
- Ignition Key Switch:
- Confirm key switch is turned ON. The ECM requires +12V power to transmit telemetry.
- Voltage Levels (4V/5V TTL vs 12V OBD2 Transceivers):
- Disconnected Yamaha YDS K-Line floats around ~4V DC (5V TTL logic level).
- Standard 12V OBD2 transceivers (e.g. L9637D, MC33660) powered by 13.8V have a ~7.0V–9.6V receiver threshold and will ignore 4V pulses!
- Fix: Supply +5V (instead of 12V) to OBD2 Pin 16, or use a 5V USB-to-TTL serial adapter.
- Multimeter Engine Connector Checklist:
- Ground (Black Wire): 0 Ω resistance to battery negative.
- Power (Red Wire): +12.5V to +14.4V DC to Ground.
- Data Wire (Yellow/White): +10V to +12V DC to Ground (disconnected ECU side). If Data wire reads 0V DC when disconnected, check harness wiring or ECU main relay.
To capture raw packets when running official Windows YDS software in a VirtualBox VM:
Best for VirtualBox Serial Port Passthrough (COM1)
-
Start the proxy script on Linux:
python3 sniff_yds.py
This creates a virtual serial port symlink
/tmp/ttyYDS. -
In VirtualBox VM Settings
$\rightarrow$ Serial Ports:- Enable Serial Port 1
- Port Mode: Host Device / Host Pipe
- Path/Address:
/tmp/ttyYDS
-
Launch Windows YDS inside the VM. All
[VM -> ECU]requests and[ECU -> VM]responses are logged in real-time to stdout andyds_sniff_log.txt.
Best for VirtualBox USB Device Passthrough
-
Enable the Linux kernel
usbmonmodule:sudo modprobe usbmon sudo chmod 666 /dev/usbmon* -
Run the raw USB packet capture script:
python3 tools/sniff_usbmon_raw.py --bus 1
All low-level USB URB transfer packets will be logged to stdout and
logs/yds_usb_raw_sniff.log.
Through low-level USB URB packet capture (logs/yds_usb_raw_sniff.log) and empirical hardware testing, the exact protocol required to unlock and read Yamaha YDS ECUs (e.g. F150 / 63P-8591A-01) 100% standalone on Raspberry Pi / Linux without Windows or VirtualBox has been decoded:
When starting from a cold ignition power-on, standard 9600-baud queries receive only 1-byte TX echoes because the ECU's diagnostic UART comparator is in sleep mode. To wake up and unlock the ECU:
- Open FTDI Serial Port (
/dev/ttyUSB0//dev/ttyUSB-yds). - Switch to 2400 Baud (
ser.baudrate = 2400). - Switch to 300 Baud (
ser.baudrate = 300) and transmit 1-byte sync opcode0x1C. - Switch to 9600 Baud (
ser.baudrate = 9600). - Assert DTR & RTS Lines HIGH (
ser.dtr = True,ser.rts = True).
This hardware baud-rate transition triggers the K-Line level shifter and wakes up the ECU's diagnostic UART interrupt.
Once unlocked, the ECU requires every polling cycle to execute a structured frame sequence:
| Opcode(s) | Parameter | Type | Conversion / Formula | Example Raw |
|---|---|---|---|---|
0x1C |
Diagnostic Sync / Heartbeat | 8-bit | Handshake & Watchdog Reset | Payload 0x03
|
0xFD |
Diagnostic Session Unlock | 8-bit | Diagnostic Mode Unlock | Payload 0x00
|
0xE8 / 0xE9
|
Total Engine Hours | 16-bit |
0x0152 (338) |
|
0xFE |
ECU Status | 8-bit | Status Flags | Payload 0x00
|
0xFF / 0x02
|
ECU Model Sub-ID | 8-bit | Hardware Identification |
0x06 |
0xDE / 0xD0
|
Subsystem Unlock | 8-bit | Diagnostic Mode Flags | Payload 0x00
|
0x91 |
Engine Temperature | 8-bit |
0x44 (68 °F) 0x6D (109 °F) |
|
0xEF / 0x1B
|
Intake Air Temperature | 8-bit |
0x33 (51) |
|
0x08 / 0x09
|
Throttle Position (TPS) | 16-bit |
|
0x02EB (747) 0x0304 (772) |
0x0B / 0x05
|
Manifold Pressure (MAP) | 8-bit | Running: |
0x8B (139) 0x3B (59) |
0x05 / 0x51
|
Barometric Pressure | 8-bit |
0xF1 (241) |
|
0x41 / 0x0D
|
ISC Valve Opening | 8-bit |
0x6D (109) 0xA3 (163) |
|
0x0E / 0x0F
|
Oil Pressure | 16-bit |
0x0A11 (2577) |
|
0x02 / 0x03
|
Battery Voltage | 16-bit |
0x0176 (374) |
|
0x0E / 0x0F
|
Fuel Injector Duration | 16-bit |
0x0A02 (2562) |
|
0xF1 |
Streaming Enable | 8-bit | Streaming Session Keep-Alive | Refresh every |
- The Yamaha ECU maintains an internal 45-second diagnostic session watchdog timer. If no keep-alive command (
0xF1) is received for 45 seconds, the ECU drops back to non-diagnostic mode. - app/yds_reader.py automatically handles periodic 8-second keep-alive heartbeats and includes automatic re-activation recovery if the connection is ever interrupted.
You can record raw Yamaha ECU opcode responses to a file on your boat or laptop and replay them offline to test and refine calibration formulas without needing the boat or engine running.
Run tools/raw_logger.py to record real-time raw opcode snapshots from the ECU to a JSON Lines (.jsonl) log file. By default, it sweeps all 256 ECU opcodes (0x00 through 0xFF) to capture complete raw diagnostic data:
# Record full 256-opcode ECU sweep (0x00 - 0xFF) at 1 Hz
python3 tools/raw_logger.py --port /dev/ttyUSB0
# Record high-rate 5 Hz stream of primary 31 telemetry opcodes
python3 tools/raw_logger.py --port /dev/ttyUSB0 --primary-only --rate 5.0
# Record to a specific file for a fixed duration (e.g., 60 seconds)
python3 tools/raw_logger.py --port /dev/ttyUSB0 --output logs/engine_run_idle.jsonl --duration 60Replay recorded .jsonl raw telemetry logs, view real-time decoded output, test modified formulas, or export to CSV:
# Replay raw log frame-by-frame in real-time
python3 tools/replay_raw.py --input logs/engine_run_idle.jsonl
# Replay at 2x speed and export decoded telemetry to CSV
python3 tools/replay_raw.py --input logs/engine_run_idle.jsonl --speed 2.0 --export-csv calibration_test.csv
# Instant full-file calibration analysis (0x speed)
python3 tools/replay_raw.py --input logs/engine_run_idle.jsonl --speed 0Stream recorded raw ECU log files directly to the web dashboard UI:
python3 app/server.py --replay-file logs/engine_run_idle.jsonl --gps-port /dev/ttyACM0 --web-port 8000