cable-station 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cable_station-0.1.0/.gitignore +10 -0
- cable_station-0.1.0/CHANGELOG.md +16 -0
- cable_station-0.1.0/LICENSE +21 -0
- cable_station-0.1.0/PKG-INFO +202 -0
- cable_station-0.1.0/README.md +169 -0
- cable_station-0.1.0/pyproject.toml +55 -0
- cable_station-0.1.0/src/cable_station/__init__.py +3 -0
- cable_station-0.1.0/src/cable_station/cli.py +182 -0
- cable_station-0.1.0/src/cable_station/config.py +124 -0
- cable_station-0.1.0/src/cable_station/flag.py +125 -0
- cable_station-0.1.0/src/cable_station/mac_reader.py +200 -0
- cable_station-0.1.0/src/cable_station/records.py +226 -0
- cable_station-0.1.0/src/cable_station/station.py +577 -0
- cable_station-0.1.0/src/cable_station/tester_capture.py +83 -0
- cable_station-0.1.0/src/cable_station/throughput.py +65 -0
- cable_station-0.1.0/tests/fixtures/emarker-screen-try2.log +131 -0
- cable_station-0.1.0/tests/fixtures/emarker-screen-try4.log +29 -0
- cable_station-0.1.0/tests/fixtures/ioreg-ports.plist +2330 -0
- cable_station-0.1.0/tests/test_cli_and_config.py +65 -0
- cable_station-0.1.0/tests/test_flag.py +63 -0
- cable_station-0.1.0/tests/test_mac_reader.py +45 -0
- cable_station-0.1.0/tests/test_records.py +70 -0
- cable_station-0.1.0/tests/test_station.py +105 -0
- cable_station-0.1.0/tests/test_tester_capture.py +52 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-11-12)
|
|
4
|
+
|
|
5
|
+
First release. Merges two earlier codebases into one package: a Mac-side reader of cable e-markers through the port registry, and a Textual station around the POWER-Z KM003C tester.
|
|
6
|
+
|
|
7
|
+
- `cable-station` (the station): modes 1 tester, 2 drive, 3 dock, 4 lightning; a mode bar; preview before print; Enter, r, n, w, x, u, s, c, p keys; a highlighted table of every recorded cable.
|
|
8
|
+
- Thunderbolt labels TB3, TB4, and TB5 with a drawn bolt; a chip that answers with no data link reads as `PWR ONLY · no data` (data wires failed).
|
|
9
|
+
- Marked BETA, untested in the title, the main box, and the README.
|
|
10
|
+
- `cable-station snapshot [--json]`: every USB-C port's chip, link, and partner (macOS).
|
|
11
|
+
- `cable-station decode LOG`: the cable's e-marker from a `km003c pd capture --raw` log.
|
|
12
|
+
- `cable-station print ID`: reprint a recorded cable's flag.
|
|
13
|
+
- `cable-station doctor`: check the tools and folders.
|
|
14
|
+
- Records as Markdown notes with frontmatter plus a generated Obsidian Base.
|
|
15
|
+
- Settings from `~/.config/cable-station/config.toml` and `CABLE_STATION_*` environment variables.
|
|
16
|
+
- The read-only throughput test is kept as a module, off by default (`snapshot --read-test VOLUME`).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 John Randall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cable-station
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: BETA, untested: read USB-C cable e-markers with a POWER-Z KM003C tester or a Mac, keep one Markdown note per cable, and print a wrap-flag label for each
|
|
5
|
+
Project-URL: Homepage, https://github.com/johntrandall/cable-station
|
|
6
|
+
Project-URL: Repository, https://github.com/johntrandall/cable-station
|
|
7
|
+
Project-URL: Issues, https://github.com/johntrandall/cable-station/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/johntrandall/cable-station/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: John Randall <john@johnrandall.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cable,e-marker,km003c,label,obsidian,power-z,ptouch,textual,usb-c,usb-pd
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: System :: Hardware
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: pillow>=10
|
|
28
|
+
Requires-Dist: textual<9,>=8
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# cable-station
|
|
35
|
+
|
|
36
|
+
**BETA, untested.** Version 0.1.0 is the first public packaging of a tool that has run on one machine; expect rough edges, and check the first few labels by hand.
|
|
37
|
+
|
|
38
|
+
Read what a USB-C cable can really do, keep one note per cable, and print a small flag that says so.
|
|
39
|
+
|
|
40
|
+
**Start here.** `cable-station` is a terminal program for sorting a drawer of USB-C cables. Every full-featured USB-C cable carries an **e-marker**, a chip that states the cable's ratings: how many amps it may carry, whether it supports 240 W Extended Power Range (EPR), which data speed it is wired for, and whether it is passive or active. The station reads that chip in one of two ways, records the result as a Markdown note, and prints a two-line wrap-flag label on a Brother P-touch so the answer travels with the cable. It was built around the ChargerLAB POWER-Z KM003C, a USB-C power and Power Delivery tester, and it also works with nothing but an Apple Silicon Mac.
|
|
41
|
+
|
|
42
|
+
## In plain terms
|
|
43
|
+
|
|
44
|
+
You have twenty black USB-C cables and no idea which one charges the laptop at full speed, which one is safe for the fast drive, and which one is a phone-charging cable that only does USB 2.0. Cables do not say. This program asks the chip inside each cable, writes down the answer, and prints a label such as `USB4 20G · USB 3.2 10G` over `100W 5A · #17` that you wrap around the cable.
|
|
45
|
+
|
|
46
|
+
- **One pass, labelled for good:** seat each cable once, press Enter, and the cable comes out of the drawer with a flag.
|
|
47
|
+
- **The answer comes from the cable itself:** the ratings are read from the e-marker chip, not guessed from the plug or the brand.
|
|
48
|
+
- **Every cable gets a number:** `#17` on the flag matches note `C17.md`, so a cable can be retested, retired, or found again.
|
|
49
|
+
- **Your records stay yours:** the notes are plain Markdown files in a folder you choose, readable in any editor and as a table in Obsidian.
|
|
50
|
+
|
|
51
|
+
## What it does
|
|
52
|
+
|
|
53
|
+
The station shows one table: the pending reading on top, every recorded cable below, and the rows whose chip identity matches the pending reading highlighted. Pressing Enter opens a preview of the flag; pressing Enter again prints it and writes the note. Two paths feed the pending reading:
|
|
54
|
+
|
|
55
|
+
1. **Tester path (primary).** The POWER-Z KM003C sits on USB with the cable under test seated in its socket. You run the tester's `USB-C eMarker` module (Application › Modules › USB-C eMarker); the station runs `km003c pd capture --raw` in the background, catches the cable's Discover Identity answer on the cable channel (SOP'), and decodes the ID Header and Cable VDO per the USB Power Delivery specification.
|
|
56
|
+
2. **Mac path.** On macOS, the Mac's own port controller asks the cable the same question when a device is plugged in through it. `cable-station` reads the answer from the I/O Registry (`Port-USB-C@N / CC / SOP'`) together with the link that trained (USB 2, USB 3, Thunderbolt or USB4) and the device on the far end. Modes 2 (drive) and 3 (dock) watch the ports for a partner device you name in the config.
|
|
57
|
+
|
|
58
|
+
A cable with no chip is, by the USB-C specification, a 3 A, 60 W, USB 2.0 cable, and the label says so. A cable that connects but never carries data is labelled `PWR ONLY · no data`; when its chip still answers, the note records the claim and says the data wires failed.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv tool install cable-station
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
or, on macOS with Homebrew:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
brew install johntrandall/tap/cable-station
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Python 3.11 or newer, Textual 8 and Pillow are the only Python requirements. Two optional tools unlock the two optional features:
|
|
73
|
+
|
|
74
|
+
- **Tester path:** the `km003c` CLI from [okhsunrog/km003c-rs](https://github.com/okhsunrog/km003c-rs) (MIT or Apache-2.0). Build it with `cargo build --release -p km003c-cli` in a clone of that repository and put the binary on your PATH, or set `km003c = "/path/to/km003c"` in the config. The pull request [km003c-rs #67](https://github.com/okhsunrog/km003c-rs/pull/67) adds a `km003c cable` command that reads the e-marker directly; until it lands, the station uses the capture route described above.
|
|
75
|
+
- **Printing:** [`ptouch-label`](https://github.com/johntrandall/ptouch-label), which drives Brother PT-P750W and PT-P950NW printers over the network: `uv tool install git+https://github.com/johntrandall/ptouch-label`. Without it, the station still renders every flag as a PNG in its state folder.
|
|
76
|
+
|
|
77
|
+
`cable-station doctor` tells you which of these it can see.
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cable-station doctor # what is installed, where the records go
|
|
83
|
+
cable-station # the station (mode 1, tester)
|
|
84
|
+
cable-station snapshot # macOS: every USB-C port's chip, link, and partner, right now
|
|
85
|
+
cable-station decode capture.log # the e-marker from a saved `km003c pd capture --raw` log
|
|
86
|
+
cable-station print 17 # reprint cable #17's flag
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A `snapshot` on a Mac with a dock on port 3 looks like this:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
port 1: connected True · nothing · link DisplayPort 8.1 Gbps (HBR3) · no e-marker answered
|
|
93
|
+
port 2: connected False · nothing · link no link · no e-marker answered
|
|
94
|
+
port 3: connected True · TS5 Plus / USB3 HUB · link Thunderbolt/USB4 40 Gb/s · chip vendor 0x2B1D product 0x192B passive ('USB4 20G · USB 3.2 10G', '240W 5A EPR') VDO 0x420A4E42
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## The keys
|
|
98
|
+
|
|
99
|
+
| Key | What it does |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `Enter` | Preview the pending reading's flag; `Enter` again prints it with a new number and writes the note. With nothing pending, reprints the highlighted row. |
|
|
102
|
+
| `r` | Reprint the highlighted cable's flag (no new note). |
|
|
103
|
+
| `1` `2` `3` `4` | Mode: `1` tester (readings come from the POWER-Z capture), `2` drive, `3` dock (the Mac reads the cable plugged between it and the named partner), `4` lightning (an iPhone or iPad enumerating on the Mac is the reading). |
|
|
104
|
+
| `c` `p` | In lightning mode: `c` cycles the other end's plug (Lightning, USB-A to Lightning, USB-C to Lightning); `p` stages a power-only Lightning cable. |
|
|
105
|
+
| `n` | Stage the seated cable as chipless and untested (`no eMarker · USB 2.0 480M` over `60W 3A`); `Enter` prints it. |
|
|
106
|
+
| `w` | Write the pending reading into the highlighted row, a retest that keeps the number; press `w` twice. |
|
|
107
|
+
| `x` | Decommission the highlighted cable (broken or discarded); the record and number are kept; press `x` twice. `u` undoes it. |
|
|
108
|
+
| `s` | Discard the pending reading. |
|
|
109
|
+
| `q` | Quit; the capture stops with the station. |
|
|
110
|
+
|
|
111
|
+
Green rows share the pending reading's chip identity (vendor, product, cable VDO): the same cable again, or an identical one. Cyan rows are chipless cables of the same speed tier, which have no identity to compare.
|
|
112
|
+
|
|
113
|
+
## The label
|
|
114
|
+
|
|
115
|
+
Two lines on 9 mm tape, printed twice on one strip with a 20 mm wrap zone and a dotted fold line between, so both faces of the flag read the same.
|
|
116
|
+
|
|
117
|
+
| Line | Content | Examples |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| 1, data | Every mode the cable supports, highest first, separated by a middle dot, then `Active` on an active cable; a Thunderbolt link trained on the Mac names the generation with a bolt | `USB 2.0 480M` · `USB 3.2 5G` · `USB4 20G · USB 3.2 10G` · `USB4 ≥40G · USB 3.2 10G Active` · `TB4 ⚡ 40G · USB 3.2 10G` |
|
|
120
|
+
| 2, power | Watts, amps, `EPR` on 240 W cables, then the cable's number | `60W 3A · #3` · `100W 5A · #17` · `240W 5A EPR · #21` |
|
|
121
|
+
| no chip | Line 1 names the fact and the tested data speed; line 2 the specification default | `no eMarker · USB 2.0 480M` / `60W 3A · #8` |
|
|
122
|
+
| power only | A cable that charges but never carries data | `PWR ONLY · no data` / `60W 3A · #9` |
|
|
123
|
+
|
|
124
|
+
`≥40G` means the chip's speed code says USB4 Gen 3: verified to 40G and possibly higher, until an 80G cable has been read on this firmware. Watts come first because that is the question at the drawer; amps second because chargers and docks talk in amps. Nothing else goes on the flag: a missing marker means passive and not EPR.
|
|
125
|
+
|
|
126
|
+
## The data layout
|
|
127
|
+
|
|
128
|
+
Records live in the data folder (default `~/cable-station/cables`), one Markdown note per cable named `C<N>.md`. The frontmatter holds the fields; the body is free text.
|
|
129
|
+
|
|
130
|
+
```markdown
|
|
131
|
+
---
|
|
132
|
+
type: cable
|
|
133
|
+
id: C17
|
|
134
|
+
tested: 2026-11-12
|
|
135
|
+
connector: USB-C to USB-C
|
|
136
|
+
data_label: "USB4 20G · USB 3.2 10G"
|
|
137
|
+
power_label: "100W 5A · #17"
|
|
138
|
+
current_a: 5
|
|
139
|
+
epr: false
|
|
140
|
+
speed: "USB4 20G · USB 3.2 10G"
|
|
141
|
+
active: false
|
|
142
|
+
emarker: true
|
|
143
|
+
emarker_read: true
|
|
144
|
+
vendor: 0x2B1D
|
|
145
|
+
product: 0x192B
|
|
146
|
+
cable_vdo: 0x00084042
|
|
147
|
+
status: active
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
# C17 · USB4 20G · USB 3.2 10G · 100W 5A · #17
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Beside the notes the station keeps `_cables.base`, an [Obsidian Base](https://help.obsidian.md/bases) that shows the folder as a table (view `1 · CABLES`). Point an Obsidian vault at the folder, or make the folder part of one. The station's own files (the capture log, each cable's raw capture, the rendered flag PNGs) go to the state folder, default `~/.local/state/cable-station`, and are not records.
|
|
154
|
+
|
|
155
|
+
## Configuration
|
|
156
|
+
|
|
157
|
+
Settings come from `~/.config/cable-station/config.toml`, overridden by `CABLE_STATION_*` environment variables. Every key is optional.
|
|
158
|
+
|
|
159
|
+
```toml
|
|
160
|
+
data_dir = "~/cable-station/cables" # the records (CABLE_STATION_DATA)
|
|
161
|
+
state_dir = "~/.local/state/cable-station" # logs, captures, previews (CABLE_STATION_STATE)
|
|
162
|
+
printer_model = "PT-P950NW" # ptouch-label --model (CABLE_STATION_PRINTER)
|
|
163
|
+
tape_mm = 9 # 6, 9, 12, 18 or 24 (CABLE_STATION_TAPE)
|
|
164
|
+
km003c = "km003c" # the tester CLI (CABLE_STATION_KM003C)
|
|
165
|
+
capture_hours = 3 # one capture run before it restarts
|
|
166
|
+
connector = "USB-C to USB-C" # recorded on new cables
|
|
167
|
+
vertical_offset_mm = 1.45 # your printer's vertical calibration offset
|
|
168
|
+
|
|
169
|
+
[partners]
|
|
170
|
+
drive = ["Envoy", "Sabrent"] # mode 2 watches ports where one of these appears (CABLE_STATION_DRIVE_PARTNERS)
|
|
171
|
+
dock = ["TS5"] # mode 3 (CABLE_STATION_DOCK_PARTNERS)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Partner names are matched against what macOS reports for the device on the far end of the cable. With no partners configured, modes 2 and 3 watch every port that gains a device.
|
|
175
|
+
|
|
176
|
+
## Limits
|
|
177
|
+
|
|
178
|
+
- **No throughput test yet.** A read-only "real work" test through the cable exists as a module (`cable-station snapshot --read-test VOLUME`) but is experimental and does not feed the label; its floors are provisional.
|
|
179
|
+
- **No power test yet.** The label's watts are what the chip claims. A worn cable still claims its rating; a voltage-drop test under load is planned, not built.
|
|
180
|
+
- **The Mac reader is macOS only.** It reads the I/O Registry. On Linux the tester path, the records, and printing all work.
|
|
181
|
+
- **The tester's eMarker module is a button press.** The KM003C sends the Discover Identity only while its `USB-C eMarker` screen is open, so each cable is one hands-on step on the tester. The vendor's serial protocol-trigger route does not reach the cable in the socket; a direct read is the subject of km003c-rs pull request #67.
|
|
182
|
+
- **Chipless does not mean USB 2.0.** Only the tester proves "no e-marker"; the Mac proves the link that trained. A Mac-only reading of a 20G or faster link proves a chip exists and is recorded as `emarker_read: false` until the tester reads it.
|
|
183
|
+
- **Verified on one machine.** The PD 2.0 versus 3.x layout of the cable VDO is chosen from ioreg's `Specification Revision`; checked on an M3 Max MacBook Pro running macOS 15, not against every Mac.
|
|
184
|
+
|
|
185
|
+
## Status
|
|
186
|
+
|
|
187
|
+
Beta, untested beyond one machine. The station has labelled one household's cable drawer; the vocabulary and keys are settled, the packaging is new. Next: the direct `km003c cable` read once km003c-rs #67 lands, a power test, and the throughput test as an opt-in.
|
|
188
|
+
|
|
189
|
+
## Credits
|
|
190
|
+
|
|
191
|
+
- [ChargerLAB POWER-Z KM003C](https://www.chargerlab.com/km003c-km002c-technical-support), the tester.
|
|
192
|
+
- [okhsunrog/km003c-rs](https://github.com/okhsunrog/km003c-rs) (MIT or Apache-2.0), the open-source driver and CLI for the tester, and [okhsunrog/km003c-protocol-research](https://github.com/okhsunrog/km003c-protocol-research), the protocol reference.
|
|
193
|
+
- [ptouch-label](https://github.com/johntrandall/ptouch-label), the P-touch printing tool.
|
|
194
|
+
- [Textual](https://textual.textualize.io/) for the terminal interface and [Pillow](https://python-pillow.org/) for the flag renderer.
|
|
195
|
+
|
|
196
|
+
## Contributing
|
|
197
|
+
|
|
198
|
+
Issues and pull requests at [github.com/johntrandall/cable-station](https://github.com/johntrandall/cable-station/issues). A capture log from a cable the decoder gets wrong is the most useful bug report: `km003c pd capture --raw --duration 60 > capture.log` while you open the tester's eMarker module, then attach the log.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
[MIT](https://github.com/johntrandall/cable-station/blob/main/LICENSE), John Randall.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# cable-station
|
|
2
|
+
|
|
3
|
+
**BETA, untested.** Version 0.1.0 is the first public packaging of a tool that has run on one machine; expect rough edges, and check the first few labels by hand.
|
|
4
|
+
|
|
5
|
+
Read what a USB-C cable can really do, keep one note per cable, and print a small flag that says so.
|
|
6
|
+
|
|
7
|
+
**Start here.** `cable-station` is a terminal program for sorting a drawer of USB-C cables. Every full-featured USB-C cable carries an **e-marker**, a chip that states the cable's ratings: how many amps it may carry, whether it supports 240 W Extended Power Range (EPR), which data speed it is wired for, and whether it is passive or active. The station reads that chip in one of two ways, records the result as a Markdown note, and prints a two-line wrap-flag label on a Brother P-touch so the answer travels with the cable. It was built around the ChargerLAB POWER-Z KM003C, a USB-C power and Power Delivery tester, and it also works with nothing but an Apple Silicon Mac.
|
|
8
|
+
|
|
9
|
+
## In plain terms
|
|
10
|
+
|
|
11
|
+
You have twenty black USB-C cables and no idea which one charges the laptop at full speed, which one is safe for the fast drive, and which one is a phone-charging cable that only does USB 2.0. Cables do not say. This program asks the chip inside each cable, writes down the answer, and prints a label such as `USB4 20G · USB 3.2 10G` over `100W 5A · #17` that you wrap around the cable.
|
|
12
|
+
|
|
13
|
+
- **One pass, labelled for good:** seat each cable once, press Enter, and the cable comes out of the drawer with a flag.
|
|
14
|
+
- **The answer comes from the cable itself:** the ratings are read from the e-marker chip, not guessed from the plug or the brand.
|
|
15
|
+
- **Every cable gets a number:** `#17` on the flag matches note `C17.md`, so a cable can be retested, retired, or found again.
|
|
16
|
+
- **Your records stay yours:** the notes are plain Markdown files in a folder you choose, readable in any editor and as a table in Obsidian.
|
|
17
|
+
|
|
18
|
+
## What it does
|
|
19
|
+
|
|
20
|
+
The station shows one table: the pending reading on top, every recorded cable below, and the rows whose chip identity matches the pending reading highlighted. Pressing Enter opens a preview of the flag; pressing Enter again prints it and writes the note. Two paths feed the pending reading:
|
|
21
|
+
|
|
22
|
+
1. **Tester path (primary).** The POWER-Z KM003C sits on USB with the cable under test seated in its socket. You run the tester's `USB-C eMarker` module (Application › Modules › USB-C eMarker); the station runs `km003c pd capture --raw` in the background, catches the cable's Discover Identity answer on the cable channel (SOP'), and decodes the ID Header and Cable VDO per the USB Power Delivery specification.
|
|
23
|
+
2. **Mac path.** On macOS, the Mac's own port controller asks the cable the same question when a device is plugged in through it. `cable-station` reads the answer from the I/O Registry (`Port-USB-C@N / CC / SOP'`) together with the link that trained (USB 2, USB 3, Thunderbolt or USB4) and the device on the far end. Modes 2 (drive) and 3 (dock) watch the ports for a partner device you name in the config.
|
|
24
|
+
|
|
25
|
+
A cable with no chip is, by the USB-C specification, a 3 A, 60 W, USB 2.0 cable, and the label says so. A cable that connects but never carries data is labelled `PWR ONLY · no data`; when its chip still answers, the note records the claim and says the data wires failed.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv tool install cable-station
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
or, on macOS with Homebrew:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
brew install johntrandall/tap/cable-station
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Python 3.11 or newer, Textual 8 and Pillow are the only Python requirements. Two optional tools unlock the two optional features:
|
|
40
|
+
|
|
41
|
+
- **Tester path:** the `km003c` CLI from [okhsunrog/km003c-rs](https://github.com/okhsunrog/km003c-rs) (MIT or Apache-2.0). Build it with `cargo build --release -p km003c-cli` in a clone of that repository and put the binary on your PATH, or set `km003c = "/path/to/km003c"` in the config. The pull request [km003c-rs #67](https://github.com/okhsunrog/km003c-rs/pull/67) adds a `km003c cable` command that reads the e-marker directly; until it lands, the station uses the capture route described above.
|
|
42
|
+
- **Printing:** [`ptouch-label`](https://github.com/johntrandall/ptouch-label), which drives Brother PT-P750W and PT-P950NW printers over the network: `uv tool install git+https://github.com/johntrandall/ptouch-label`. Without it, the station still renders every flag as a PNG in its state folder.
|
|
43
|
+
|
|
44
|
+
`cable-station doctor` tells you which of these it can see.
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cable-station doctor # what is installed, where the records go
|
|
50
|
+
cable-station # the station (mode 1, tester)
|
|
51
|
+
cable-station snapshot # macOS: every USB-C port's chip, link, and partner, right now
|
|
52
|
+
cable-station decode capture.log # the e-marker from a saved `km003c pd capture --raw` log
|
|
53
|
+
cable-station print 17 # reprint cable #17's flag
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A `snapshot` on a Mac with a dock on port 3 looks like this:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
port 1: connected True · nothing · link DisplayPort 8.1 Gbps (HBR3) · no e-marker answered
|
|
60
|
+
port 2: connected False · nothing · link no link · no e-marker answered
|
|
61
|
+
port 3: connected True · TS5 Plus / USB3 HUB · link Thunderbolt/USB4 40 Gb/s · chip vendor 0x2B1D product 0x192B passive ('USB4 20G · USB 3.2 10G', '240W 5A EPR') VDO 0x420A4E42
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## The keys
|
|
65
|
+
|
|
66
|
+
| Key | What it does |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `Enter` | Preview the pending reading's flag; `Enter` again prints it with a new number and writes the note. With nothing pending, reprints the highlighted row. |
|
|
69
|
+
| `r` | Reprint the highlighted cable's flag (no new note). |
|
|
70
|
+
| `1` `2` `3` `4` | Mode: `1` tester (readings come from the POWER-Z capture), `2` drive, `3` dock (the Mac reads the cable plugged between it and the named partner), `4` lightning (an iPhone or iPad enumerating on the Mac is the reading). |
|
|
71
|
+
| `c` `p` | In lightning mode: `c` cycles the other end's plug (Lightning, USB-A to Lightning, USB-C to Lightning); `p` stages a power-only Lightning cable. |
|
|
72
|
+
| `n` | Stage the seated cable as chipless and untested (`no eMarker · USB 2.0 480M` over `60W 3A`); `Enter` prints it. |
|
|
73
|
+
| `w` | Write the pending reading into the highlighted row, a retest that keeps the number; press `w` twice. |
|
|
74
|
+
| `x` | Decommission the highlighted cable (broken or discarded); the record and number are kept; press `x` twice. `u` undoes it. |
|
|
75
|
+
| `s` | Discard the pending reading. |
|
|
76
|
+
| `q` | Quit; the capture stops with the station. |
|
|
77
|
+
|
|
78
|
+
Green rows share the pending reading's chip identity (vendor, product, cable VDO): the same cable again, or an identical one. Cyan rows are chipless cables of the same speed tier, which have no identity to compare.
|
|
79
|
+
|
|
80
|
+
## The label
|
|
81
|
+
|
|
82
|
+
Two lines on 9 mm tape, printed twice on one strip with a 20 mm wrap zone and a dotted fold line between, so both faces of the flag read the same.
|
|
83
|
+
|
|
84
|
+
| Line | Content | Examples |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| 1, data | Every mode the cable supports, highest first, separated by a middle dot, then `Active` on an active cable; a Thunderbolt link trained on the Mac names the generation with a bolt | `USB 2.0 480M` · `USB 3.2 5G` · `USB4 20G · USB 3.2 10G` · `USB4 ≥40G · USB 3.2 10G Active` · `TB4 ⚡ 40G · USB 3.2 10G` |
|
|
87
|
+
| 2, power | Watts, amps, `EPR` on 240 W cables, then the cable's number | `60W 3A · #3` · `100W 5A · #17` · `240W 5A EPR · #21` |
|
|
88
|
+
| no chip | Line 1 names the fact and the tested data speed; line 2 the specification default | `no eMarker · USB 2.0 480M` / `60W 3A · #8` |
|
|
89
|
+
| power only | A cable that charges but never carries data | `PWR ONLY · no data` / `60W 3A · #9` |
|
|
90
|
+
|
|
91
|
+
`≥40G` means the chip's speed code says USB4 Gen 3: verified to 40G and possibly higher, until an 80G cable has been read on this firmware. Watts come first because that is the question at the drawer; amps second because chargers and docks talk in amps. Nothing else goes on the flag: a missing marker means passive and not EPR.
|
|
92
|
+
|
|
93
|
+
## The data layout
|
|
94
|
+
|
|
95
|
+
Records live in the data folder (default `~/cable-station/cables`), one Markdown note per cable named `C<N>.md`. The frontmatter holds the fields; the body is free text.
|
|
96
|
+
|
|
97
|
+
```markdown
|
|
98
|
+
---
|
|
99
|
+
type: cable
|
|
100
|
+
id: C17
|
|
101
|
+
tested: 2026-11-12
|
|
102
|
+
connector: USB-C to USB-C
|
|
103
|
+
data_label: "USB4 20G · USB 3.2 10G"
|
|
104
|
+
power_label: "100W 5A · #17"
|
|
105
|
+
current_a: 5
|
|
106
|
+
epr: false
|
|
107
|
+
speed: "USB4 20G · USB 3.2 10G"
|
|
108
|
+
active: false
|
|
109
|
+
emarker: true
|
|
110
|
+
emarker_read: true
|
|
111
|
+
vendor: 0x2B1D
|
|
112
|
+
product: 0x192B
|
|
113
|
+
cable_vdo: 0x00084042
|
|
114
|
+
status: active
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
# C17 · USB4 20G · USB 3.2 10G · 100W 5A · #17
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Beside the notes the station keeps `_cables.base`, an [Obsidian Base](https://help.obsidian.md/bases) that shows the folder as a table (view `1 · CABLES`). Point an Obsidian vault at the folder, or make the folder part of one. The station's own files (the capture log, each cable's raw capture, the rendered flag PNGs) go to the state folder, default `~/.local/state/cable-station`, and are not records.
|
|
121
|
+
|
|
122
|
+
## Configuration
|
|
123
|
+
|
|
124
|
+
Settings come from `~/.config/cable-station/config.toml`, overridden by `CABLE_STATION_*` environment variables. Every key is optional.
|
|
125
|
+
|
|
126
|
+
```toml
|
|
127
|
+
data_dir = "~/cable-station/cables" # the records (CABLE_STATION_DATA)
|
|
128
|
+
state_dir = "~/.local/state/cable-station" # logs, captures, previews (CABLE_STATION_STATE)
|
|
129
|
+
printer_model = "PT-P950NW" # ptouch-label --model (CABLE_STATION_PRINTER)
|
|
130
|
+
tape_mm = 9 # 6, 9, 12, 18 or 24 (CABLE_STATION_TAPE)
|
|
131
|
+
km003c = "km003c" # the tester CLI (CABLE_STATION_KM003C)
|
|
132
|
+
capture_hours = 3 # one capture run before it restarts
|
|
133
|
+
connector = "USB-C to USB-C" # recorded on new cables
|
|
134
|
+
vertical_offset_mm = 1.45 # your printer's vertical calibration offset
|
|
135
|
+
|
|
136
|
+
[partners]
|
|
137
|
+
drive = ["Envoy", "Sabrent"] # mode 2 watches ports where one of these appears (CABLE_STATION_DRIVE_PARTNERS)
|
|
138
|
+
dock = ["TS5"] # mode 3 (CABLE_STATION_DOCK_PARTNERS)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Partner names are matched against what macOS reports for the device on the far end of the cable. With no partners configured, modes 2 and 3 watch every port that gains a device.
|
|
142
|
+
|
|
143
|
+
## Limits
|
|
144
|
+
|
|
145
|
+
- **No throughput test yet.** A read-only "real work" test through the cable exists as a module (`cable-station snapshot --read-test VOLUME`) but is experimental and does not feed the label; its floors are provisional.
|
|
146
|
+
- **No power test yet.** The label's watts are what the chip claims. A worn cable still claims its rating; a voltage-drop test under load is planned, not built.
|
|
147
|
+
- **The Mac reader is macOS only.** It reads the I/O Registry. On Linux the tester path, the records, and printing all work.
|
|
148
|
+
- **The tester's eMarker module is a button press.** The KM003C sends the Discover Identity only while its `USB-C eMarker` screen is open, so each cable is one hands-on step on the tester. The vendor's serial protocol-trigger route does not reach the cable in the socket; a direct read is the subject of km003c-rs pull request #67.
|
|
149
|
+
- **Chipless does not mean USB 2.0.** Only the tester proves "no e-marker"; the Mac proves the link that trained. A Mac-only reading of a 20G or faster link proves a chip exists and is recorded as `emarker_read: false` until the tester reads it.
|
|
150
|
+
- **Verified on one machine.** The PD 2.0 versus 3.x layout of the cable VDO is chosen from ioreg's `Specification Revision`; checked on an M3 Max MacBook Pro running macOS 15, not against every Mac.
|
|
151
|
+
|
|
152
|
+
## Status
|
|
153
|
+
|
|
154
|
+
Beta, untested beyond one machine. The station has labelled one household's cable drawer; the vocabulary and keys are settled, the packaging is new. Next: the direct `km003c cable` read once km003c-rs #67 lands, a power test, and the throughput test as an opt-in.
|
|
155
|
+
|
|
156
|
+
## Credits
|
|
157
|
+
|
|
158
|
+
- [ChargerLAB POWER-Z KM003C](https://www.chargerlab.com/km003c-km002c-technical-support), the tester.
|
|
159
|
+
- [okhsunrog/km003c-rs](https://github.com/okhsunrog/km003c-rs) (MIT or Apache-2.0), the open-source driver and CLI for the tester, and [okhsunrog/km003c-protocol-research](https://github.com/okhsunrog/km003c-protocol-research), the protocol reference.
|
|
160
|
+
- [ptouch-label](https://github.com/johntrandall/ptouch-label), the P-touch printing tool.
|
|
161
|
+
- [Textual](https://textual.textualize.io/) for the terminal interface and [Pillow](https://python-pillow.org/) for the flag renderer.
|
|
162
|
+
|
|
163
|
+
## Contributing
|
|
164
|
+
|
|
165
|
+
Issues and pull requests at [github.com/johntrandall/cable-station](https://github.com/johntrandall/cable-station/issues). A capture log from a cable the decoder gets wrong is the most useful bug report: `km003c pd capture --raw --duration 60 > capture.log` while you open the tester's eMarker module, then attach the log.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
[MIT](https://github.com/johntrandall/cable-station/blob/main/LICENSE), John Randall.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cable-station"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "BETA, untested: read USB-C cable e-markers with a POWER-Z KM003C tester or a Mac, keep one Markdown note per cable, and print a wrap-flag label for each"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "John Randall", email = "john@johnrandall.com" }]
|
|
9
|
+
keywords = ["usb-c", "e-marker", "usb-pd", "cable", "label", "km003c", "power-z", "ptouch", "textual", "obsidian"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: End Users/Desktop",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: MacOS",
|
|
16
|
+
"Operating System :: POSIX :: Linux",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: System :: Hardware",
|
|
23
|
+
"Topic :: Utilities",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"textual>=8,<9",
|
|
27
|
+
"pillow>=10",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.23"]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
cable-station = "cable_station.cli:main"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/johntrandall/cable-station"
|
|
38
|
+
Repository = "https://github.com/johntrandall/cable-station"
|
|
39
|
+
Issues = "https://github.com/johntrandall/cable-station/issues"
|
|
40
|
+
Changelog = "https://github.com/johntrandall/cable-station/blob/main/CHANGELOG.md"
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["hatchling"]
|
|
44
|
+
build-backend = "hatchling.build"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/cable_station"]
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.sdist]
|
|
50
|
+
include = ["src/cable_station", "tests", "README.md", "LICENSE", "CHANGELOG.md", "pyproject.toml"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|
|
54
|
+
asyncio_mode = "auto"
|
|
55
|
+
asyncio_default_fixture_loop_scope = "function"
|