connects to real port, and pair port, and prints traffic
commit
c5113f2b6c
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Serial Proxy (MITM)
|
||||||
|
|
||||||
|
A small Python app that sits between the Arduino IDE and a real COM device. It creates (or uses) a **virtual COM port** that you open in the Arduino IDE, while it talks to the **real device** on another port. All traffic is **recorded** both ways and **passed through unchanged**.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
1. **CLI** — Lists connected COM ports; you choose the real device (e.g. your Arduino).
|
||||||
|
2. **Virtual port** — Tries to create a virtual COM pair with [com0com](https://sourceforge.net/projects/com0com/); if that fails, you pick one end of an existing pair.
|
||||||
|
3. **Relay** — Data from Arduino IDE → virtual port → this app → real device, and the reverse. No modification, only copying.
|
||||||
|
4. **Logging** — Every byte in both directions is logged to `serial_log.txt` (and optionally to the console) with timestamps and hex.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- [pyserial](https://pypi.org/project/pyserial/): `pip install -r requirements.txt`
|
||||||
|
- **Virtual COM pair** (for “create virtual port” to work): install [com0com](https://sourceforge.net/projects/com0com/) and, if you want the app to create pairs automatically, run the script (or `setupc.exe`) with **Administrator** rights so it can create port pairs.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
2. (Optional) Install com0com and, if needed, run the app as Administrator so it can create a virtual pair.
|
||||||
|
3. Run the proxy:
|
||||||
|
```bash
|
||||||
|
python serial_proxy.py
|
||||||
|
```
|
||||||
|
4. Select the **real device** COM port (e.g. the actual Arduino).
|
||||||
|
5. If the app created a virtual pair, it will tell you which port to select in the Arduino IDE (e.g. COM252). Otherwise, select the end of the pair that **this app** will use; in the Arduino IDE you must select the **other** end of that pair.
|
||||||
|
6. Enter baud rate (default 115200) or press Enter.
|
||||||
|
7. In the Arduino IDE, choose the indicated virtual port and use Serial Monitor / upload as usual. All traffic is logged to `serial_log.txt`.
|
||||||
|
|
||||||
|
Stop with **Ctrl+C**.
|
||||||
|
|
||||||
|
## Log file
|
||||||
|
|
||||||
|
- Path: `serial_log.txt` in the same folder as `serial_proxy.py`.
|
||||||
|
- Each line has timestamp, direction (`IDE->device` or `device->IDE`), byte count, hex dump, and a raw repr of the bytes.
|
||||||
|
|
||||||
|
## Without com0com
|
||||||
|
|
||||||
|
If you don’t install com0com, the app cannot create a virtual pair. You’ll need another way to get a **pair** of linked virtual ports (e.g. another null-modem/virtual serial driver). Then run the app, select the **real** device port, and when asked, select the port that **this app** should open (one end of the pair). Use the **other** end of that pair in the Arduino IDE.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| You select in the app | You select in Arduino IDE |
|
||||||
|
|------------------------|----------------------------|
|
||||||
|
| Real device (e.g. COM3) | — |
|
||||||
|
| Virtual port “for this app” (e.g. COM251) | The **other** end of the pair (e.g. COM252) |
|
||||||
|
|
||||||
|
Traffic flows: **Arduino IDE ↔ virtual pair ↔ this app ↔ real device**, with full logging both ways.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pyserial>=3.5
|
||||||
|
|
@ -0,0 +1,633 @@
|
||||||
|
|
||||||
|
--- session started 2026-02-16T14:35:58.705768 ---
|
||||||
|
real=COM77 virtual_ours=COM251 virtual_ide=COM252 baud=115200
|
||||||
|
|
||||||
|
--- session started 2026-02-16T14:38:30.817526 ---
|
||||||
|
real=COM77 virtual_ours=COM251 virtual_ide=COM252 baud=115200
|
||||||
|
|
||||||
|
--- session started 2026-02-16T14:50:38.812196 ---
|
||||||
|
real=COM77 virtual_ours=COM1 virtual_ide=COM2 baud=115200
|
||||||
|
[14:50:39.067] device->IDE (8 bytes)
|
||||||
|
hex: 3732303130360d0a
|
||||||
|
raw: b'720106\r\n'
|
||||||
|
[14:50:39.561] device->IDE (8 bytes)
|
||||||
|
hex: 3732303630360d0a
|
||||||
|
raw: b'720606\r\n'
|
||||||
|
[14:50:40.065] device->IDE (8 bytes)
|
||||||
|
hex: 3732313130360d0a
|
||||||
|
raw: b'721106\r\n'
|
||||||
|
[14:50:40.561] device->IDE (8 bytes)
|
||||||
|
hex: 3732313630360d0a
|
||||||
|
raw: b'721606\r\n'
|
||||||
|
[14:50:41.067] device->IDE (8 bytes)
|
||||||
|
hex: 3732323130360d0a
|
||||||
|
raw: b'722106\r\n'
|
||||||
|
[14:50:41.561] device->IDE (8 bytes)
|
||||||
|
hex: 3732323630360d0a
|
||||||
|
raw: b'722606\r\n'
|
||||||
|
[14:50:42.066] device->IDE (8 bytes)
|
||||||
|
hex: 3732333130360d0a
|
||||||
|
raw: b'723106\r\n'
|
||||||
|
[14:50:42.562] device->IDE (8 bytes)
|
||||||
|
hex: 3732333630360d0a
|
||||||
|
raw: b'723606\r\n'
|
||||||
|
[14:50:43.067] device->IDE (8 bytes)
|
||||||
|
hex: 3732343130360d0a
|
||||||
|
raw: b'724106\r\n'
|
||||||
|
[14:50:43.564] device->IDE (8 bytes)
|
||||||
|
hex: 3732343630360d0a
|
||||||
|
raw: b'724606\r\n'
|
||||||
|
[14:50:44.070] device->IDE (8 bytes)
|
||||||
|
hex: 3732353130360d0a
|
||||||
|
raw: b'725106\r\n'
|
||||||
|
[14:50:44.568] device->IDE (8 bytes)
|
||||||
|
hex: 3732353630360d0a
|
||||||
|
raw: b'725606\r\n'
|
||||||
|
[14:50:45.065] device->IDE (8 bytes)
|
||||||
|
hex: 3732363130360d0a
|
||||||
|
raw: b'726106\r\n'
|
||||||
|
[14:50:45.562] device->IDE (8 bytes)
|
||||||
|
hex: 3732363630360d0a
|
||||||
|
raw: b'726606\r\n'
|
||||||
|
[14:50:46.066] device->IDE (8 bytes)
|
||||||
|
hex: 3732373130360d0a
|
||||||
|
raw: b'727106\r\n'
|
||||||
|
[14:50:46.563] device->IDE (8 bytes)
|
||||||
|
hex: 3732373630360d0a
|
||||||
|
raw: b'727606\r\n'
|
||||||
|
[14:50:47.067] device->IDE (8 bytes)
|
||||||
|
hex: 3732383130360d0a
|
||||||
|
raw: b'728106\r\n'
|
||||||
|
[14:50:47.563] device->IDE (8 bytes)
|
||||||
|
hex: 3732383630360d0a
|
||||||
|
raw: b'728606\r\n'
|
||||||
|
[14:50:48.071] device->IDE (8 bytes)
|
||||||
|
hex: 3732393130360d0a
|
||||||
|
raw: b'729106\r\n'
|
||||||
|
[14:50:48.566] device->IDE (8 bytes)
|
||||||
|
hex: 3732393630360d0a
|
||||||
|
raw: b'729606\r\n'
|
||||||
|
[14:50:49.064] device->IDE (8 bytes)
|
||||||
|
hex: 3733303130360d0a
|
||||||
|
raw: b'730106\r\n'
|
||||||
|
[14:50:49.565] device->IDE (8 bytes)
|
||||||
|
hex: 3733303630360d0a
|
||||||
|
raw: b'730606\r\n'
|
||||||
|
[14:50:50.061] device->IDE (8 bytes)
|
||||||
|
hex: 3733313130360d0a
|
||||||
|
raw: b'731106\r\n'
|
||||||
|
[14:50:50.564] device->IDE (8 bytes)
|
||||||
|
hex: 3733313630360d0a
|
||||||
|
raw: b'731606\r\n'
|
||||||
|
[14:50:51.070] device->IDE (8 bytes)
|
||||||
|
hex: 3733323130360d0a
|
||||||
|
raw: b'732106\r\n'
|
||||||
|
[14:50:51.566] device->IDE (8 bytes)
|
||||||
|
hex: 3733323630360d0a
|
||||||
|
raw: b'732606\r\n'
|
||||||
|
[14:50:52.069] device->IDE (8 bytes)
|
||||||
|
hex: 3733333130360d0a
|
||||||
|
raw: b'733106\r\n'
|
||||||
|
[14:50:52.561] device->IDE (8 bytes)
|
||||||
|
hex: 3733333630360d0a
|
||||||
|
raw: b'733606\r\n'
|
||||||
|
[14:50:53.064] device->IDE (8 bytes)
|
||||||
|
hex: 3733343130360d0a
|
||||||
|
raw: b'734106\r\n'
|
||||||
|
[14:50:53.568] device->IDE (8 bytes)
|
||||||
|
hex: 3733343630360d0a
|
||||||
|
raw: b'734606\r\n'
|
||||||
|
[14:50:54.061] device->IDE (8 bytes)
|
||||||
|
hex: 3733353130360d0a
|
||||||
|
raw: b'735106\r\n'
|
||||||
|
--- session ended ---
|
||||||
|
|
||||||
|
--- session started 2026-02-16T14:51:04.003069 ---
|
||||||
|
real=COM77 virtual_ours=COM1 virtual_ide=COM2 baud=115200
|
||||||
|
[14:51:04.288] device->IDE (6 bytes)
|
||||||
|
hex: 393530360d0a
|
||||||
|
raw: b'9506\r\n'
|
||||||
|
[14:51:04.783] device->IDE (7 bytes)
|
||||||
|
hex: 31303030360d0a
|
||||||
|
raw: b'10006\r\n'
|
||||||
|
[14:51:05.285] device->IDE (7 bytes)
|
||||||
|
hex: 31303530360d0a
|
||||||
|
raw: b'10506\r\n'
|
||||||
|
[14:51:05.788] device->IDE (7 bytes)
|
||||||
|
hex: 31313030360d0a
|
||||||
|
raw: b'11006\r\n'
|
||||||
|
[14:51:06.281] device->IDE (7 bytes)
|
||||||
|
hex: 31313530360d0a
|
||||||
|
raw: b'11506\r\n'
|
||||||
|
[14:51:06.788] device->IDE (7 bytes)
|
||||||
|
hex: 31323030360d0a
|
||||||
|
raw: b'12006\r\n'
|
||||||
|
[14:51:07.284] device->IDE (7 bytes)
|
||||||
|
hex: 31323530360d0a
|
||||||
|
raw: b'12506\r\n'
|
||||||
|
[14:51:07.788] device->IDE (7 bytes)
|
||||||
|
hex: 31333030360d0a
|
||||||
|
raw: b'13006\r\n'
|
||||||
|
[14:51:08.282] device->IDE (7 bytes)
|
||||||
|
hex: 31333530360d0a
|
||||||
|
raw: b'13506\r\n'
|
||||||
|
[14:51:08.788] device->IDE (7 bytes)
|
||||||
|
hex: 31343030360d0a
|
||||||
|
raw: b'14006\r\n'
|
||||||
|
[14:51:09.284] device->IDE (7 bytes)
|
||||||
|
hex: 31343530360d0a
|
||||||
|
raw: b'14506\r\n'
|
||||||
|
[14:51:09.779] device->IDE (7 bytes)
|
||||||
|
hex: 31353030360d0a
|
||||||
|
raw: b'15006\r\n'
|
||||||
|
[14:51:10.284] device->IDE (7 bytes)
|
||||||
|
hex: 31353530360d0a
|
||||||
|
raw: b'15506\r\n'
|
||||||
|
[14:51:10.778] device->IDE (7 bytes)
|
||||||
|
hex: 31363030360d0a
|
||||||
|
raw: b'16006\r\n'
|
||||||
|
[14:51:11.284] device->IDE (7 bytes)
|
||||||
|
hex: 31363530360d0a
|
||||||
|
raw: b'16506\r\n'
|
||||||
|
[14:51:11.786] device->IDE (7 bytes)
|
||||||
|
hex: 31373030360d0a
|
||||||
|
raw: b'17006\r\n'
|
||||||
|
[14:51:12.280] device->IDE (7 bytes)
|
||||||
|
hex: 31373530360d0a
|
||||||
|
raw: b'17506\r\n'
|
||||||
|
[14:51:12.784] device->IDE (7 bytes)
|
||||||
|
hex: 31383030360d0a
|
||||||
|
raw: b'18006\r\n'
|
||||||
|
[14:51:13.280] device->IDE (7 bytes)
|
||||||
|
hex: 31383530360d0a
|
||||||
|
raw: b'18506\r\n'
|
||||||
|
[14:51:13.781] device->IDE (7 bytes)
|
||||||
|
hex: 31393030360d0a
|
||||||
|
raw: b'19006\r\n'
|
||||||
|
[14:51:14.279] device->IDE (7 bytes)
|
||||||
|
hex: 31393530360d0a
|
||||||
|
raw: b'19506\r\n'
|
||||||
|
[14:51:14.784] device->IDE (7 bytes)
|
||||||
|
hex: 32303030360d0a
|
||||||
|
raw: b'20006\r\n'
|
||||||
|
[14:51:15.282] device->IDE (7 bytes)
|
||||||
|
hex: 32303530360d0a
|
||||||
|
raw: b'20506\r\n'
|
||||||
|
[14:51:15.788] device->IDE (7 bytes)
|
||||||
|
hex: 32313030360d0a
|
||||||
|
raw: b'21006\r\n'
|
||||||
|
[14:51:16.281] device->IDE (7 bytes)
|
||||||
|
hex: 32313530360d0a
|
||||||
|
raw: b'21506\r\n'
|
||||||
|
[14:51:16.785] device->IDE (7 bytes)
|
||||||
|
hex: 32323030360d0a
|
||||||
|
raw: b'22006\r\n'
|
||||||
|
[14:51:17.280] device->IDE (7 bytes)
|
||||||
|
hex: 32323530360d0a
|
||||||
|
raw: b'22506\r\n'
|
||||||
|
[14:51:17.784] device->IDE (7 bytes)
|
||||||
|
hex: 32333030360d0a
|
||||||
|
raw: b'23006\r\n'
|
||||||
|
[14:51:18.288] device->IDE (7 bytes)
|
||||||
|
hex: 32333530360d0a
|
||||||
|
raw: b'23506\r\n'
|
||||||
|
[14:51:18.779] device->IDE (7 bytes)
|
||||||
|
hex: 32343030360d0a
|
||||||
|
raw: b'24006\r\n'
|
||||||
|
[14:51:19.282] device->IDE (7 bytes)
|
||||||
|
hex: 32343530360d0a
|
||||||
|
raw: b'24506\r\n'
|
||||||
|
[14:51:19.784] device->IDE (7 bytes)
|
||||||
|
hex: 32353030360d0a
|
||||||
|
raw: b'25006\r\n'
|
||||||
|
[14:51:20.279] device->IDE (7 bytes)
|
||||||
|
hex: 32353530360d0a
|
||||||
|
raw: b'25506\r\n'
|
||||||
|
[14:51:20.782] device->IDE (7 bytes)
|
||||||
|
hex: 32363030360d0a
|
||||||
|
raw: b'26006\r\n'
|
||||||
|
[14:51:21.283] device->IDE (7 bytes)
|
||||||
|
hex: 32363530360d0a
|
||||||
|
raw: b'26506\r\n'
|
||||||
|
[14:51:21.787] device->IDE (7 bytes)
|
||||||
|
hex: 32373030360d0a
|
||||||
|
raw: b'27006\r\n'
|
||||||
|
[14:51:22.288] device->IDE (7 bytes)
|
||||||
|
hex: 32373530360d0a
|
||||||
|
raw: b'27506\r\n'
|
||||||
|
[14:51:22.780] device->IDE (7 bytes)
|
||||||
|
hex: 32383030360d0a
|
||||||
|
raw: b'28006\r\n'
|
||||||
|
[14:51:23.282] device->IDE (7 bytes)
|
||||||
|
hex: 32383530360d0a
|
||||||
|
raw: b'28506\r\n'
|
||||||
|
[14:51:23.785] device->IDE (7 bytes)
|
||||||
|
hex: 32393030360d0a
|
||||||
|
raw: b'29006\r\n'
|
||||||
|
[14:51:24.281] device->IDE (7 bytes)
|
||||||
|
hex: 32393530360d0a
|
||||||
|
raw: b'29506\r\n'
|
||||||
|
[14:51:24.784] device->IDE (7 bytes)
|
||||||
|
hex: 33303030360d0a
|
||||||
|
raw: b'30006\r\n'
|
||||||
|
[14:51:25.280] device->IDE (7 bytes)
|
||||||
|
hex: 33303530360d0a
|
||||||
|
raw: b'30506\r\n'
|
||||||
|
[14:51:25.785] device->IDE (7 bytes)
|
||||||
|
hex: 33313030360d0a
|
||||||
|
raw: b'31006\r\n'
|
||||||
|
[14:51:26.288] device->IDE (7 bytes)
|
||||||
|
hex: 33313530360d0a
|
||||||
|
raw: b'31506\r\n'
|
||||||
|
[14:51:26.782] device->IDE (7 bytes)
|
||||||
|
hex: 33323030360d0a
|
||||||
|
raw: b'32006\r\n'
|
||||||
|
[14:51:27.284] device->IDE (7 bytes)
|
||||||
|
hex: 33323530360d0a
|
||||||
|
raw: b'32506\r\n'
|
||||||
|
[14:51:27.788] device->IDE (7 bytes)
|
||||||
|
hex: 33333030360d0a
|
||||||
|
raw: b'33006\r\n'
|
||||||
|
[14:51:28.281] device->IDE (7 bytes)
|
||||||
|
hex: 33333530360d0a
|
||||||
|
raw: b'33506\r\n'
|
||||||
|
[14:51:28.783] device->IDE (7 bytes)
|
||||||
|
hex: 33343030360d0a
|
||||||
|
raw: b'34006\r\n'
|
||||||
|
[14:51:29.289] device->IDE (7 bytes)
|
||||||
|
hex: 33343530360d0a
|
||||||
|
raw: b'34506\r\n'
|
||||||
|
[14:51:29.786] device->IDE (7 bytes)
|
||||||
|
hex: 33353030360d0a
|
||||||
|
raw: b'35006\r\n'
|
||||||
|
[14:51:30.283] device->IDE (7 bytes)
|
||||||
|
hex: 33353530360d0a
|
||||||
|
raw: b'35506\r\n'
|
||||||
|
[14:51:30.789] device->IDE (7 bytes)
|
||||||
|
hex: 33363030360d0a
|
||||||
|
raw: b'36006\r\n'
|
||||||
|
[14:51:31.288] device->IDE (7 bytes)
|
||||||
|
hex: 33363530360d0a
|
||||||
|
raw: b'36506\r\n'
|
||||||
|
[14:51:31.787] device->IDE (7 bytes)
|
||||||
|
hex: 33373030360d0a
|
||||||
|
raw: b'37006\r\n'
|
||||||
|
[14:51:32.285] device->IDE (7 bytes)
|
||||||
|
hex: 33373530360d0a
|
||||||
|
raw: b'37506\r\n'
|
||||||
|
[14:51:32.830] device->IDE (7 bytes)
|
||||||
|
hex: 33383030360d0a
|
||||||
|
raw: b'38006\r\n'
|
||||||
|
[14:51:33.287] device->IDE (7 bytes)
|
||||||
|
hex: 33383530360d0a
|
||||||
|
raw: b'38506\r\n'
|
||||||
|
[14:51:33.785] device->IDE (7 bytes)
|
||||||
|
hex: 33393030360d0a
|
||||||
|
raw: b'39006\r\n'
|
||||||
|
[14:51:34.330] device->IDE (7 bytes)
|
||||||
|
hex: 33393530360d0a
|
||||||
|
raw: b'39506\r\n'
|
||||||
|
[14:51:34.789] device->IDE (7 bytes)
|
||||||
|
hex: 34303030360d0a
|
||||||
|
raw: b'40006\r\n'
|
||||||
|
[14:51:35.284] device->IDE (7 bytes)
|
||||||
|
hex: 34303530360d0a
|
||||||
|
raw: b'40506\r\n'
|
||||||
|
[14:51:35.780] device->IDE (7 bytes)
|
||||||
|
hex: 34313030360d0a
|
||||||
|
raw: b'41006\r\n'
|
||||||
|
[14:51:36.286] device->IDE (7 bytes)
|
||||||
|
hex: 34313530360d0a
|
||||||
|
raw: b'41506\r\n'
|
||||||
|
[14:51:36.785] device->IDE (7 bytes)
|
||||||
|
hex: 34323030360d0a
|
||||||
|
raw: b'42006\r\n'
|
||||||
|
[14:51:37.285] device->IDE (7 bytes)
|
||||||
|
hex: 34323530360d0a
|
||||||
|
raw: b'42506\r\n'
|
||||||
|
[14:51:37.784] device->IDE (7 bytes)
|
||||||
|
hex: 34333030360d0a
|
||||||
|
raw: b'43006\r\n'
|
||||||
|
[14:51:38.281] device->IDE (7 bytes)
|
||||||
|
hex: 34333530360d0a
|
||||||
|
raw: b'43506\r\n'
|
||||||
|
[14:51:38.787] device->IDE (7 bytes)
|
||||||
|
hex: 34343030360d0a
|
||||||
|
raw: b'44006\r\n'
|
||||||
|
[14:51:39.285] device->IDE (7 bytes)
|
||||||
|
hex: 34343530360d0a
|
||||||
|
raw: b'44506\r\n'
|
||||||
|
[14:51:39.780] device->IDE (7 bytes)
|
||||||
|
hex: 34353030360d0a
|
||||||
|
raw: b'45006\r\n'
|
||||||
|
[14:51:40.289] device->IDE (7 bytes)
|
||||||
|
hex: 34353530360d0a
|
||||||
|
raw: b'45506\r\n'
|
||||||
|
[14:51:40.838] device->IDE (7 bytes)
|
||||||
|
hex: 34363030360d0a
|
||||||
|
raw: b'46006\r\n'
|
||||||
|
[14:51:41.284] device->IDE (7 bytes)
|
||||||
|
hex: 34363530360d0a
|
||||||
|
raw: b'46506\r\n'
|
||||||
|
[14:51:41.784] device->IDE (7 bytes)
|
||||||
|
hex: 34373030360d0a
|
||||||
|
raw: b'47006\r\n'
|
||||||
|
[14:51:42.283] device->IDE (7 bytes)
|
||||||
|
hex: 34373530360d0a
|
||||||
|
raw: b'47506\r\n'
|
||||||
|
[14:51:42.782] device->IDE (7 bytes)
|
||||||
|
hex: 34383030360d0a
|
||||||
|
raw: b'48006\r\n'
|
||||||
|
[14:51:43.280] device->IDE (7 bytes)
|
||||||
|
hex: 34383530360d0a
|
||||||
|
raw: b'48506\r\n'
|
||||||
|
[14:51:43.780] device->IDE (7 bytes)
|
||||||
|
hex: 34393030360d0a
|
||||||
|
raw: b'49006\r\n'
|
||||||
|
[14:51:44.282] device->IDE (7 bytes)
|
||||||
|
hex: 34393530360d0a
|
||||||
|
raw: b'49506\r\n'
|
||||||
|
[14:51:44.783] device->IDE (7 bytes)
|
||||||
|
hex: 35303030360d0a
|
||||||
|
raw: b'50006\r\n'
|
||||||
|
[14:51:45.280] device->IDE (7 bytes)
|
||||||
|
hex: 35303530360d0a
|
||||||
|
raw: b'50506\r\n'
|
||||||
|
[14:51:45.787] device->IDE (7 bytes)
|
||||||
|
hex: 35313030360d0a
|
||||||
|
raw: b'51006\r\n'
|
||||||
|
[14:51:46.286] device->IDE (7 bytes)
|
||||||
|
hex: 35313530360d0a
|
||||||
|
raw: b'51506\r\n'
|
||||||
|
[14:51:46.783] device->IDE (7 bytes)
|
||||||
|
hex: 35323030360d0a
|
||||||
|
raw: b'52006\r\n'
|
||||||
|
[14:51:47.282] device->IDE (7 bytes)
|
||||||
|
hex: 35323530360d0a
|
||||||
|
raw: b'52506\r\n'
|
||||||
|
[14:51:47.780] device->IDE (7 bytes)
|
||||||
|
hex: 35333030360d0a
|
||||||
|
raw: b'53006\r\n'
|
||||||
|
[14:51:48.290] device->IDE (7 bytes)
|
||||||
|
hex: 35333530360d0a
|
||||||
|
raw: b'53506\r\n'
|
||||||
|
[14:51:48.787] device->IDE (7 bytes)
|
||||||
|
hex: 35343030360d0a
|
||||||
|
raw: b'54006\r\n'
|
||||||
|
[14:51:49.284] device->IDE (7 bytes)
|
||||||
|
hex: 35343530360d0a
|
||||||
|
raw: b'54506\r\n'
|
||||||
|
[14:51:49.782] device->IDE (7 bytes)
|
||||||
|
hex: 35353030360d0a
|
||||||
|
raw: b'55006\r\n'
|
||||||
|
[14:51:50.282] device->IDE (7 bytes)
|
||||||
|
hex: 35353530360d0a
|
||||||
|
raw: b'55506\r\n'
|
||||||
|
[14:51:50.782] device->IDE (7 bytes)
|
||||||
|
hex: 35363030360d0a
|
||||||
|
raw: b'56006\r\n'
|
||||||
|
[14:51:51.280] device->IDE (7 bytes)
|
||||||
|
hex: 35363530360d0a
|
||||||
|
raw: b'56506\r\n'
|
||||||
|
[14:51:51.786] device->IDE (7 bytes)
|
||||||
|
hex: 35373030360d0a
|
||||||
|
raw: b'57006\r\n'
|
||||||
|
[14:51:52.284] device->IDE (7 bytes)
|
||||||
|
hex: 35373530360d0a
|
||||||
|
raw: b'57506\r\n'
|
||||||
|
[14:51:52.782] device->IDE (7 bytes)
|
||||||
|
hex: 35383030360d0a
|
||||||
|
raw: b'58006\r\n'
|
||||||
|
[14:51:53.281] device->IDE (7 bytes)
|
||||||
|
hex: 35383530360d0a
|
||||||
|
raw: b'58506\r\n'
|
||||||
|
[14:51:53.788] device->IDE (7 bytes)
|
||||||
|
hex: 35393030360d0a
|
||||||
|
raw: b'59006\r\n'
|
||||||
|
[14:51:54.286] device->IDE (7 bytes)
|
||||||
|
hex: 35393530360d0a
|
||||||
|
raw: b'59506\r\n'
|
||||||
|
[14:51:54.781] device->IDE (7 bytes)
|
||||||
|
hex: 36303030360d0a
|
||||||
|
raw: b'60006\r\n'
|
||||||
|
[14:51:55.289] device->IDE (7 bytes)
|
||||||
|
hex: 36303530360d0a
|
||||||
|
raw: b'60506\r\n'
|
||||||
|
[14:51:55.787] device->IDE (7 bytes)
|
||||||
|
hex: 36313030360d0a
|
||||||
|
raw: b'61006\r\n'
|
||||||
|
[14:51:56.286] device->IDE (7 bytes)
|
||||||
|
hex: 36313530360d0a
|
||||||
|
raw: b'61506\r\n'
|
||||||
|
[14:51:56.780] device->IDE (7 bytes)
|
||||||
|
hex: 36323030360d0a
|
||||||
|
raw: b'62006\r\n'
|
||||||
|
[14:51:57.290] device->IDE (7 bytes)
|
||||||
|
hex: 36323530360d0a
|
||||||
|
raw: b'62506\r\n'
|
||||||
|
[14:51:57.788] device->IDE (7 bytes)
|
||||||
|
hex: 36333030360d0a
|
||||||
|
raw: b'63006\r\n'
|
||||||
|
[14:51:58.337] device->IDE (7 bytes)
|
||||||
|
hex: 36333530360d0a
|
||||||
|
raw: b'63506\r\n'
|
||||||
|
[14:51:58.781] device->IDE (7 bytes)
|
||||||
|
hex: 36343030360d0a
|
||||||
|
raw: b'64006\r\n'
|
||||||
|
[14:51:59.289] device->IDE (7 bytes)
|
||||||
|
hex: 36343530360d0a
|
||||||
|
raw: b'64506\r\n'
|
||||||
|
[14:51:59.787] device->IDE (7 bytes)
|
||||||
|
hex: 36353030360d0a
|
||||||
|
raw: b'65006\r\n'
|
||||||
|
[14:52:00.285] device->IDE (7 bytes)
|
||||||
|
hex: 36353530360d0a
|
||||||
|
raw: b'65506\r\n'
|
||||||
|
[14:52:00.781] device->IDE (7 bytes)
|
||||||
|
hex: 36363030360d0a
|
||||||
|
raw: b'66006\r\n'
|
||||||
|
[14:52:01.285] device->IDE (7 bytes)
|
||||||
|
hex: 36363530360d0a
|
||||||
|
raw: b'66506\r\n'
|
||||||
|
[14:52:01.840] device->IDE (7 bytes)
|
||||||
|
hex: 36373030360d0a
|
||||||
|
raw: b'67006\r\n'
|
||||||
|
[14:52:02.287] device->IDE (7 bytes)
|
||||||
|
hex: 36373530360d0a
|
||||||
|
raw: b'67506\r\n'
|
||||||
|
[14:52:02.782] device->IDE (7 bytes)
|
||||||
|
hex: 36383030360d0a
|
||||||
|
raw: b'68006\r\n'
|
||||||
|
[14:52:03.286] device->IDE (7 bytes)
|
||||||
|
hex: 36383530360d0a
|
||||||
|
raw: b'68506\r\n'
|
||||||
|
[14:52:03.789] device->IDE (7 bytes)
|
||||||
|
hex: 36393030360d0a
|
||||||
|
raw: b'69006\r\n'
|
||||||
|
[14:52:04.283] device->IDE (7 bytes)
|
||||||
|
hex: 36393530360d0a
|
||||||
|
raw: b'69506\r\n'
|
||||||
|
[14:52:04.786] device->IDE (7 bytes)
|
||||||
|
hex: 37303030360d0a
|
||||||
|
raw: b'70006\r\n'
|
||||||
|
[14:52:05.281] device->IDE (7 bytes)
|
||||||
|
hex: 37303530360d0a
|
||||||
|
raw: b'70506\r\n'
|
||||||
|
[14:52:05.784] device->IDE (7 bytes)
|
||||||
|
hex: 37313030360d0a
|
||||||
|
raw: b'71006\r\n'
|
||||||
|
[14:52:06.286] device->IDE (7 bytes)
|
||||||
|
hex: 37313530360d0a
|
||||||
|
raw: b'71506\r\n'
|
||||||
|
[14:52:06.833] device->IDE (7 bytes)
|
||||||
|
hex: 37323030360d0a
|
||||||
|
raw: b'72006\r\n'
|
||||||
|
[14:52:07.289] device->IDE (7 bytes)
|
||||||
|
hex: 37323530360d0a
|
||||||
|
raw: b'72506\r\n'
|
||||||
|
[14:52:07.787] device->IDE (7 bytes)
|
||||||
|
hex: 37333030360d0a
|
||||||
|
raw: b'73006\r\n'
|
||||||
|
[14:52:08.280] device->IDE (7 bytes)
|
||||||
|
hex: 37333530360d0a
|
||||||
|
raw: b'73506\r\n'
|
||||||
|
--- session ended ---
|
||||||
|
|
||||||
|
--- session started 2026-02-16T14:55:12.502691 ---
|
||||||
|
real=COM77 virtual_ours=COM251 virtual_ide=COM252 baud=115200
|
||||||
|
|
||||||
|
--- session started 2026-02-16T14:58:14.662362 ---
|
||||||
|
real=COM77 virtual_ours=COM1 virtual_ide=None baud=115200
|
||||||
|
[14:58:14.999] device->IDE (264 bytes)
|
||||||
|
hex: 3136333630360d0a3136343130360d0a3136343630360d0a3136353130360d0a3136353630360d0a3136363130360d0a3136363630360d0a3136373130360d0a3136373630360d0a3136383130360d0a3136383630360d0a3136393130360d0a3136393630360d0a3137303130360d0a3137303630360d0a3137313130360d0a3137313630360d0a3137323130360d0a3137323630360d0a3137333130360d0a3137333630360d0a3137343130360d0a3137343630360d0a3137353130360d0a3137353630360d0a3137363130360d0a3137363630360d0a3137373130360d0a3137373630360d0a3137383130360d0a3137383630360d0a3137393130360d0a3137393630360d0a
|
||||||
|
raw: b'163606\r\n164106\r\n164606\r\n165106\r\n165606\r\n166106\r\n166606\r\n167106\r\n167606\r\n168106\r\n168606\r\n169106\r\n169606\r\n170106\r\n170606\r\n171106\r\n171606\r\n172106\r\n172606\r\n173106\r\n173606\r\n174106\r\n174606\r\n175106\r\n175606\r\n176106\r\n176606\r\n177106\r\n177606\r\n178106\r\n178606\r\n179106\r\n179606\r\n'
|
||||||
|
[14:58:15.505] device->IDE (8 bytes)
|
||||||
|
hex: 3138303130360d0a
|
||||||
|
raw: b'180106\r\n'
|
||||||
|
[14:58:16.009] device->IDE (8 bytes)
|
||||||
|
hex: 3138303630360d0a
|
||||||
|
raw: b'180606\r\n'
|
||||||
|
[14:58:16.504] device->IDE (8 bytes)
|
||||||
|
hex: 3138313130360d0a
|
||||||
|
raw: b'181106\r\n'
|
||||||
|
[14:58:17.009] device->IDE (8 bytes)
|
||||||
|
hex: 3138313630360d0a
|
||||||
|
raw: b'181606\r\n'
|
||||||
|
[14:58:17.504] device->IDE (8 bytes)
|
||||||
|
hex: 3138323130360d0a
|
||||||
|
raw: b'182106\r\n'
|
||||||
|
[14:58:18.002] device->IDE (8 bytes)
|
||||||
|
hex: 3138323630360d0a
|
||||||
|
raw: b'182606\r\n'
|
||||||
|
[14:58:18.500] device->IDE (8 bytes)
|
||||||
|
hex: 3138333130360d0a
|
||||||
|
raw: b'183106\r\n'
|
||||||
|
[14:58:18.999] device->IDE (8 bytes)
|
||||||
|
hex: 3138333630360d0a
|
||||||
|
raw: b'183606\r\n'
|
||||||
|
[14:58:19.506] device->IDE (8 bytes)
|
||||||
|
hex: 3138343130360d0a
|
||||||
|
raw: b'184106\r\n'
|
||||||
|
[14:58:20.003] device->IDE (8 bytes)
|
||||||
|
hex: 3138343630360d0a
|
||||||
|
raw: b'184606\r\n'
|
||||||
|
[14:58:20.501] device->IDE (8 bytes)
|
||||||
|
hex: 3138353130360d0a
|
||||||
|
raw: b'185106\r\n'
|
||||||
|
[14:58:21.001] device->IDE (8 bytes)
|
||||||
|
hex: 3138353630360d0a
|
||||||
|
raw: b'185606\r\n'
|
||||||
|
[14:58:21.508] device->IDE (8 bytes)
|
||||||
|
hex: 3138363130360d0a
|
||||||
|
raw: b'186106\r\n'
|
||||||
|
[14:58:22.002] device->IDE (8 bytes)
|
||||||
|
hex: 3138363630360d0a
|
||||||
|
raw: b'186606\r\n'
|
||||||
|
[14:58:22.505] device->IDE (8 bytes)
|
||||||
|
hex: 3138373130360d0a
|
||||||
|
raw: b'187106\r\n'
|
||||||
|
[14:58:23.009] device->IDE (8 bytes)
|
||||||
|
hex: 3138373630360d0a
|
||||||
|
raw: b'187606\r\n'
|
||||||
|
[14:58:23.502] device->IDE (8 bytes)
|
||||||
|
hex: 3138383130360d0a
|
||||||
|
raw: b'188106\r\n'
|
||||||
|
[14:58:24.007] device->IDE (8 bytes)
|
||||||
|
hex: 3138383630360d0a
|
||||||
|
raw: b'188606\r\n'
|
||||||
|
[14:58:24.500] device->IDE (8 bytes)
|
||||||
|
hex: 3138393130360d0a
|
||||||
|
raw: b'189106\r\n'
|
||||||
|
[14:58:25.006] device->IDE (8 bytes)
|
||||||
|
hex: 3138393630360d0a
|
||||||
|
raw: b'189606\r\n'
|
||||||
|
[14:58:25.507] device->IDE (8 bytes)
|
||||||
|
hex: 3139303130360d0a
|
||||||
|
raw: b'190106\r\n'
|
||||||
|
[14:58:26.000] device->IDE (8 bytes)
|
||||||
|
hex: 3139303630360d0a
|
||||||
|
raw: b'190606\r\n'
|
||||||
|
[14:58:26.504] device->IDE (8 bytes)
|
||||||
|
hex: 3139313130360d0a
|
||||||
|
raw: b'191106\r\n'
|
||||||
|
[14:58:27.008] device->IDE (8 bytes)
|
||||||
|
hex: 3139313630360d0a
|
||||||
|
raw: b'191606\r\n'
|
||||||
|
[14:58:27.501] device->IDE (8 bytes)
|
||||||
|
hex: 3139323130360d0a
|
||||||
|
raw: b'192106\r\n'
|
||||||
|
[14:58:28.005] device->IDE (8 bytes)
|
||||||
|
hex: 3139323630360d0a
|
||||||
|
raw: b'192606\r\n'
|
||||||
|
[14:58:28.500] device->IDE (8 bytes)
|
||||||
|
hex: 3139333130360d0a
|
||||||
|
raw: b'193106\r\n'
|
||||||
|
[14:58:29.002] device->IDE (8 bytes)
|
||||||
|
hex: 3139333630360d0a
|
||||||
|
raw: b'193606\r\n'
|
||||||
|
[14:58:29.505] device->IDE (8 bytes)
|
||||||
|
hex: 3139343130360d0a
|
||||||
|
raw: b'194106\r\n'
|
||||||
|
[14:58:30.002] device->IDE (8 bytes)
|
||||||
|
hex: 3139343630360d0a
|
||||||
|
raw: b'194606\r\n'
|
||||||
|
[14:58:30.508] device->IDE (8 bytes)
|
||||||
|
hex: 3139353130360d0a
|
||||||
|
raw: b'195106\r\n'
|
||||||
|
[14:58:31.002] device->IDE (8 bytes)
|
||||||
|
hex: 3139353630360d0a
|
||||||
|
raw: b'195606\r\n'
|
||||||
|
[14:58:31.507] device->IDE (8 bytes)
|
||||||
|
hex: 3139363130360d0a
|
||||||
|
raw: b'196106\r\n'
|
||||||
|
[14:58:32.002] device->IDE (8 bytes)
|
||||||
|
hex: 3139363630360d0a
|
||||||
|
raw: b'196606\r\n'
|
||||||
|
[14:58:32.507] device->IDE (8 bytes)
|
||||||
|
hex: 3139373130360d0a
|
||||||
|
raw: b'197106\r\n'
|
||||||
|
[14:58:33.003] device->IDE (8 bytes)
|
||||||
|
hex: 3139373630360d0a
|
||||||
|
raw: b'197606\r\n'
|
||||||
|
[14:58:33.501] device->IDE (8 bytes)
|
||||||
|
hex: 3139383130360d0a
|
||||||
|
raw: b'198106\r\n'
|
||||||
|
[14:58:34.006] device->IDE (8 bytes)
|
||||||
|
hex: 3139383630360d0a
|
||||||
|
raw: b'198606\r\n'
|
||||||
|
[14:58:34.503] device->IDE (8 bytes)
|
||||||
|
hex: 3139393130360d0a
|
||||||
|
raw: b'199106\r\n'
|
||||||
|
[14:58:35.000] device->IDE (8 bytes)
|
||||||
|
hex: 3139393630360d0a
|
||||||
|
raw: b'199606\r\n'
|
||||||
|
[14:58:35.500] device->IDE (8 bytes)
|
||||||
|
hex: 3230303130360d0a
|
||||||
|
raw: b'200106\r\n'
|
||||||
|
[14:58:36.002] device->IDE (8 bytes)
|
||||||
|
hex: 3230303630360d0a
|
||||||
|
raw: b'200606\r\n'
|
||||||
|
[14:58:36.500] device->IDE (8 bytes)
|
||||||
|
hex: 3230313130360d0a
|
||||||
|
raw: b'201106\r\n'
|
||||||
|
--- session ended ---
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Serial port proxy (MITM): connects to a real COM device and exposes a virtual COM port
|
||||||
|
for the Arduino IDE. Records all traffic both ways while passing it through unchanged.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
|
||||||
|
# Default baud rate (Arduino Uno/Nano typically use 115200 for serial monitor)
|
||||||
|
DEFAULT_BAUD = 115200
|
||||||
|
|
||||||
|
|
||||||
|
def list_ports():
|
||||||
|
"""Return list of (port, description) for all COM ports."""
|
||||||
|
return [
|
||||||
|
(p.device, f"{p.device} — {p.description or 'Unknown'}")
|
||||||
|
for p in serial.tools.list_ports.comports()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def select_port(prompt: str, exclude: list[str] | None = None) -> str | None:
|
||||||
|
"""Show numbered list of COM ports and return selected port or None."""
|
||||||
|
ports = list_ports()
|
||||||
|
exclude = exclude or []
|
||||||
|
ports = [(dev, desc) for dev, desc in ports if dev not in exclude]
|
||||||
|
if not ports:
|
||||||
|
print("No COM ports found.")
|
||||||
|
return None
|
||||||
|
print(prompt)
|
||||||
|
for i, (dev, desc) in enumerate(ports, 1):
|
||||||
|
print(f" {i}. {desc}")
|
||||||
|
print(" 0. Cancel")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = input("Choice: ").strip()
|
||||||
|
if choice == "0":
|
||||||
|
return None
|
||||||
|
idx = int(choice)
|
||||||
|
if 1 <= idx <= len(ports):
|
||||||
|
return ports[idx - 1][0]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
print("Invalid choice.")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_port_in_use_error(exc: BaseException) -> bool:
|
||||||
|
"""True if the exception indicates the port is already open by another process."""
|
||||||
|
msg = str(exc).lower()
|
||||||
|
return (
|
||||||
|
"in use" in msg
|
||||||
|
or "access is denied" in msg
|
||||||
|
or "permission" in msg
|
||||||
|
or "permissionerror" in msg
|
||||||
|
or "error 5" in msg
|
||||||
|
or "error 32" in msg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_elevation_error(exc: BaseException) -> bool:
|
||||||
|
"""True if the exception is WinError 740 (operation requires elevation)."""
|
||||||
|
if isinstance(exc, OSError) and getattr(exc, "winerror", None) == 740:
|
||||||
|
return True
|
||||||
|
return "740" in str(exc) or "elevation" in str(exc).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def relay_loop(
|
||||||
|
name: str,
|
||||||
|
read_ser: serial.Serial,
|
||||||
|
write_ser: serial.Serial,
|
||||||
|
direction: str,
|
||||||
|
log_file,
|
||||||
|
log_console: bool,
|
||||||
|
):
|
||||||
|
"""Read from read_ser, write to write_ser, and log each chunk. Does not close ports."""
|
||||||
|
try:
|
||||||
|
while read_ser.is_open and write_ser.is_open:
|
||||||
|
data = read_ser.read(read_ser.in_waiting or 1)
|
||||||
|
if not data:
|
||||||
|
time.sleep(0.01)
|
||||||
|
continue
|
||||||
|
write_ser.write(data)
|
||||||
|
write_ser.flush()
|
||||||
|
ts = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||||
|
line = f"[{ts}] {direction} ({len(data)} bytes)\n"
|
||||||
|
if log_file:
|
||||||
|
log_file.write(line)
|
||||||
|
log_file.write(" hex: " + data.hex() + "\n")
|
||||||
|
try:
|
||||||
|
log_file.write(" raw: " + repr(data) + "\n")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
log_file.flush()
|
||||||
|
if log_console:
|
||||||
|
print(line.strip())
|
||||||
|
print(" hex:", data.hex())
|
||||||
|
except (serial.SerialException, OSError) as e:
|
||||||
|
if read_ser.is_open or write_ser.is_open:
|
||||||
|
print(f"[{name}] {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Serial proxy (MITM) — connect Arduino IDE to a virtual port while we talk to the real device.\n")
|
||||||
|
|
||||||
|
# 1) Select real device port
|
||||||
|
real_port = select_port("Select the REAL device port (e.g. your Arduino):")
|
||||||
|
if not real_port:
|
||||||
|
print("Aborted.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 2) Virtual port: you already have a pair (e.g. VSPE); choose which end this app uses
|
||||||
|
other_ports = [(d, desc) for d, desc in list_ports() if d != real_port]
|
||||||
|
if not other_ports:
|
||||||
|
print("No other COM ports found. You only have your device connected.")
|
||||||
|
print("Create a virtual port pair (e.g. in VSPE), then run this script again.")
|
||||||
|
return 1
|
||||||
|
print()
|
||||||
|
print("Select the virtual port that THIS APP will use (e.g. COM1 for VSPE).")
|
||||||
|
print("In Arduino IDE, open the OTHER end of the pair (e.g. COM2).\n")
|
||||||
|
virtual_port_ours = select_port("Virtual port for this app:", exclude=[real_port])
|
||||||
|
if not virtual_port_ours:
|
||||||
|
print("Aborted.")
|
||||||
|
return 1
|
||||||
|
virtual_port_ide = None
|
||||||
|
|
||||||
|
# Baud rate
|
||||||
|
try:
|
||||||
|
baud_str = input(f"\nBaud rate [{DEFAULT_BAUD}]: ").strip() or str(DEFAULT_BAUD)
|
||||||
|
baud = int(baud_str)
|
||||||
|
except ValueError:
|
||||||
|
baud = DEFAULT_BAUD
|
||||||
|
print(f"Using baud rate: {baud}")
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
log_path = Path(__file__).resolve().parent / "serial_log.txt"
|
||||||
|
log_console = True
|
||||||
|
try:
|
||||||
|
log_file = open(log_path, "a", encoding="utf-8")
|
||||||
|
log_file.write("\n--- session started " + datetime.now().isoformat() + " ---\n")
|
||||||
|
log_file.write(f"real={real_port} virtual_ours={virtual_port_ours} virtual_ide={virtual_port_ide} baud={baud}\n")
|
||||||
|
log_file.flush()
|
||||||
|
except OSError as e:
|
||||||
|
print(f"Could not open log file: {e}")
|
||||||
|
log_file = None
|
||||||
|
|
||||||
|
print(f"\nLogging to: {log_path}")
|
||||||
|
print("Opening ports...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ser_real = serial.Serial(real_port, baud, timeout=0)
|
||||||
|
except (serial.SerialException, OSError) as e:
|
||||||
|
print(f"Failed to open real port {real_port}: {e}")
|
||||||
|
if _is_elevation_error(e):
|
||||||
|
print(" → Windows is requesting administrator rights. Try running this script as a NORMAL user (not 'Run as administrator').")
|
||||||
|
print(" Serial ports often work without admin; running elevated can sometimes cause this error.")
|
||||||
|
elif _is_port_in_use_error(e):
|
||||||
|
print(" → Close any app using that port (Serial Monitor, another terminal, previous script), then try again.")
|
||||||
|
if log_file:
|
||||||
|
log_file.close()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Virtual port: retry a few times in case it's slow to become available
|
||||||
|
ser_virtual = None
|
||||||
|
for attempt in range(5):
|
||||||
|
try:
|
||||||
|
ser_virtual = serial.Serial(virtual_port_ours, baud, timeout=0)
|
||||||
|
break
|
||||||
|
except (serial.SerialException, OSError) as e:
|
||||||
|
in_use = _is_port_in_use_error(e)
|
||||||
|
if _is_elevation_error(e):
|
||||||
|
ser_real.close()
|
||||||
|
print(f"Failed to open virtual port {virtual_port_ours}: {e}")
|
||||||
|
print(" → Try running this script as a NORMAL user (not 'Run as administrator').")
|
||||||
|
if log_file:
|
||||||
|
log_file.close()
|
||||||
|
return 1
|
||||||
|
if attempt < 4:
|
||||||
|
print(f" Could not open {virtual_port_ours} (attempt {attempt + 1}/5), retrying in 2s...")
|
||||||
|
if in_use:
|
||||||
|
print(" → Close Arduino IDE Serial Monitor and any other app using that port, then wait for retry.")
|
||||||
|
time.sleep(2)
|
||||||
|
else:
|
||||||
|
ser_real.close()
|
||||||
|
print(f"Failed to open virtual port {virtual_port_ours}: {e}")
|
||||||
|
if in_use:
|
||||||
|
print("\nPort is in use by another process. Do this then run the script again:")
|
||||||
|
print(" • Close Arduino IDE Serial Monitor (and any other app using that port).")
|
||||||
|
print(" • Close any other window running this script.")
|
||||||
|
else:
|
||||||
|
print("\nMake sure the virtual port exists and is available, then run this script again.")
|
||||||
|
if log_file:
|
||||||
|
log_file.close()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if virtual_port_ide:
|
||||||
|
print(f"\n>>> In Arduino IDE, select port: {virtual_port_ide} <<<\n")
|
||||||
|
else:
|
||||||
|
print(f"\n>>> In Arduino IDE, select the OTHER end of the pair (not {virtual_port_ours}) <<<\n")
|
||||||
|
|
||||||
|
print("Relaying and recording. Press Ctrl+C to stop.\n")
|
||||||
|
|
||||||
|
# IDE -> device
|
||||||
|
t1 = threading.Thread(
|
||||||
|
target=relay_loop,
|
||||||
|
args=("IDE->device", ser_virtual, ser_real, "IDE->device", log_file, log_console),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
# device -> IDE
|
||||||
|
t2 = threading.Thread(
|
||||||
|
target=relay_loop,
|
||||||
|
args=("device->IDE", ser_real, ser_virtual, "device->IDE", log_file, log_console),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
t1.start()
|
||||||
|
t2.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while t1.is_alive() or t2.is_alive():
|
||||||
|
t1.join(timeout=0.5)
|
||||||
|
t2.join(timeout=0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nStopping...")
|
||||||
|
finally:
|
||||||
|
for s in (ser_real, ser_virtual):
|
||||||
|
try:
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if log_file:
|
||||||
|
log_file.write("--- session ended ---\n")
|
||||||
|
log_file.close()
|
||||||
|
|
||||||
|
print("Done.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Loading…
Reference in New Issue