bmweb-cli 0.1.0

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.
Files changed (72) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +351 -0
  3. package/dist/bmweb.js +2790 -0
  4. package/package.json +53 -0
  5. package/runtime/core/bestvm/codec.js +285 -0
  6. package/runtime/core/bestvm/environment.js +116 -0
  7. package/runtime/core/bestvm/executor.js +1483 -0
  8. package/runtime/core/bestvm/index.js +52 -0
  9. package/runtime/core/bestvm/machine.js +491 -0
  10. package/runtime/core/bestvm/operands.js +356 -0
  11. package/runtime/core/bestvm/registers.js +152 -0
  12. package/runtime/core/bestvm/write-guard.js +111 -0
  13. package/runtime/core/ipofile/compile.js +364 -0
  14. package/runtime/core/ipofile/decls.js +187 -0
  15. package/runtime/core/ipofile/emit.js +708 -0
  16. package/runtime/core/ipofile/exec.js +164 -0
  17. package/runtime/core/ipofile/lex.js +243 -0
  18. package/runtime/core/ipofile/parse.js +550 -0
  19. package/runtime/core/ipofile/pool.js +404 -0
  20. package/runtime/core/ipofile/walk.js +431 -0
  21. package/runtime/core/ipovm/builtin-helpers.js +182 -0
  22. package/runtime/core/ipovm/builtins-api.js +610 -0
  23. package/runtime/core/ipovm/builtins-screen.js +493 -0
  24. package/runtime/core/ipovm/builtins-table.js +166 -0
  25. package/runtime/core/ipovm/builtins-text.js +166 -0
  26. package/runtime/core/ipovm/emissions.js +138 -0
  27. package/runtime/core/ipovm/hosts.js +191 -0
  28. package/runtime/core/ipovm/operators.js +229 -0
  29. package/runtime/core/ipovm/structures.js +250 -0
  30. package/runtime/core/ipovm/suspensions.js +241 -0
  31. package/runtime/core/ipovm/tape.js +206 -0
  32. package/runtime/core/ipovm/values.js +241 -0
  33. package/runtime/core/ipovm/vm.js +1166 -0
  34. package/runtime/core/translate.js +526 -0
  35. package/runtime/core/webshim/api-router.js +592 -0
  36. package/runtime/core/webshim/bus.js +95 -0
  37. package/runtime/core/webshim/coding.js +82 -0
  38. package/runtime/core/webshim/data-fetch.js +66 -0
  39. package/runtime/core/webshim/exchange.js +288 -0
  40. package/runtime/core/webshim/framing.js +331 -0
  41. package/runtime/core/webshim/install.js +30 -0
  42. package/runtime/core/webshim/job-runner.js +319 -0
  43. package/runtime/core/webshim/native-bus.js +108 -0
  44. package/runtime/core/webshim/timers.js +82 -0
  45. package/runtime/core/webshim/trace.js +205 -0
  46. package/runtime/core/webshim/transport-base.js +128 -0
  47. package/runtime/core/webshim/variant-resolver.js +249 -0
  48. package/runtime/core/webshim/web-serial-bus.js +734 -0
  49. package/runtime/home/bmweb-home.ips +76 -0
  50. package/runtime/home/bmweb.h +26 -0
  51. package/runtime/screens/activations.js +258 -0
  52. package/runtime/screens/garage/diff.js +331 -0
  53. package/runtime/screens/garage/share.js +276 -0
  54. package/runtime/screens/garage/store.js +547 -0
  55. package/runtime/screens/ipo-runtime/cells.js +176 -0
  56. package/runtime/screens/ipo-runtime/dialogs.js +254 -0
  57. package/runtime/screens/ipo-runtime/home.js +358 -0
  58. package/runtime/screens/ipo-runtime/open.js +393 -0
  59. package/runtime/screens/ipo-runtime/paint-grid.js +106 -0
  60. package/runtime/screens/ipo-runtime/paint-modern.js +424 -0
  61. package/runtime/screens/ipo-runtime/print.js +281 -0
  62. package/runtime/screens/ipo-runtime/program.js +1337 -0
  63. package/runtime/screens/ipo-runtime/protocol.js +464 -0
  64. package/runtime/screens/ipo-runtime/script-scan.js +225 -0
  65. package/runtime/screens/ipo-runtime/translate-sets.js +130 -0
  66. package/runtime/screens/ipo-runtime/ui.js +249 -0
  67. package/runtime/screens/ipo-runtime/wire-policy.js +113 -0
  68. package/runtime/screens/ir.js +324 -0
  69. package/runtime/screens/search/data.js +153 -0
  70. package/runtime/screens/search/match.js +285 -0
  71. package/runtime/screens/search/open.js +66 -0
  72. package/runtime/vendor/fflate.min.js +1 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @file The same bus, over the native bridge.
3
+ *
4
+ * A WKWebView has no Web Serial -- the desktop shell's web view does not offer
5
+ * that API -- so the shell owns the port and moves
6
+ * bytes for us (SerialProxy.cs). The framing, checksums and echo handling
7
+ * stay in exchange.js, identical to the Web Serial path; only the four
8
+ * primitives (open, write, read, flush) differ.
9
+ */
10
+ /* exported NativeSerialBus */
11
+
12
+ /**
13
+ * The transport behind the macOS shell's `window.bmacw` serial bridge.
14
+ * @extends SerialTransportBase
15
+ */
16
+ class NativeSerialBus extends SerialTransportBase {
17
+ constructor() {
18
+ super();
19
+ /** @type {string|null} the device path the shell opened, or 'serial' */
20
+ this.path = null;
21
+ /** @type {PortConfig|null} */
22
+ this.config = null;
23
+ }
24
+
25
+ /** @returns {boolean} Is the shell holding a port open for us. */
26
+ get connected() {
27
+ return !!this.path;
28
+ }
29
+
30
+ /**
31
+ * Ask the shell to open its serial port at the K+DCAN default.
32
+ * @returns {Promise<string>} The port label.
33
+ */
34
+ async connect() {
35
+ const r = await window.bmacw.serialOpen(null, KDCAN.baudRate, KDCAN.parity);
36
+ this.path = (r && r.port) || 'serial';
37
+ this.config = KDCAN;
38
+ return this.portLabel();
39
+ }
40
+
41
+ /** @returns {string} The device name without its /dev/ prefix. */
42
+ portLabel() {
43
+ return (this.path || '').replace('/dev/', '');
44
+ }
45
+
46
+ /** Close the shell's port and forget it. */
47
+ async disconnect() {
48
+ try {
49
+ await window.bmacw.serialClose();
50
+ } catch {
51
+ /* already closed */
52
+ }
53
+ this.path = null;
54
+ this.config = null;
55
+ }
56
+
57
+ /**
58
+ * Reopen the port when a job's concept needs different wire settings --
59
+ * an E46 mixes 9600 8E1 body modules with a 115200 8N1 DME, and a port
60
+ * opened once at connect time can only speak to one of them.
61
+ * @param {PortConfig} cfg - The settings the next telegram needs.
62
+ */
63
+ async ensureConfig(cfg) {
64
+ if (this._configUnchanged(cfg)) return;
65
+ // Reopening the port drops the ECU session with it, so a woken module
66
+ // must be woken again. Without this a concept switch mid-job left
67
+ // `inited` set and every following request went to a sleeping ECU.
68
+ this._resetWakeState();
69
+ const r = await window.bmacw.serialOpen(
70
+ this.path === 'serial' ? null : this.path,
71
+ cfg.baudRate,
72
+ cfg.parity
73
+ );
74
+ this.path = (r && r.port) || this.path;
75
+ this.config = cfg;
76
+ }
77
+
78
+ // exchange() is inherited from SerialTransportBase (identical for every
79
+ // transport -- it delegates to the shared runExchange).
80
+
81
+ /**
82
+ * Write one framed request through the shell and read its answer.
83
+ *
84
+ * NOTE: the K-line wake (fast init / slow init) lives on WebSerialBus,
85
+ * which owns the break and DTR lines. The native bridge (window.bmacw)
86
+ * exposes no setSignals equivalent yet, so a K-line ECU reached through
87
+ * the desktop app still relies on the host side doing the wake.
88
+ * @param {number[]|null} framed - The request with its checksum, or null
89
+ * to read a continuation frame without writing.
90
+ * @param {number} timeoutMs - ParTimeoutStd for this read.
91
+ * @param {CommParams} comm - The request's wire parameters.
92
+ * @returns {Promise<number[]>} The answer frame.
93
+ */
94
+ async exchangeRaw(framed, timeoutMs, comm) {
95
+ if (framed) {
96
+ // A stale partial frame from a timed-out job would be read as this
97
+ // job's answer, so start clean.
98
+ await window.bmacw.serialFlush();
99
+ await window.bmacw.serialWrite(framed);
100
+ }
101
+ return readFrame(
102
+ framed,
103
+ timeoutMs,
104
+ async () => window.bmacw.serialRead(),
105
+ comm
106
+ );
107
+ }
108
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @file The wire path's clock: a sleep that keeps time in a background tab.
3
+ *
4
+ * Run the app with no server: static JSON for data, Web Serial for the bus.
5
+ * The core/webshim/ folder replaces the two things the C# server did:
6
+ * GET /api/... -> a frozen file in api/ (tools/web_export.py)
7
+ * POST /api/.../run/X -> the BEST2 VM (bestvm.js), talking to the cable
8
+ * The VM already runs a job against a `send(bytes)->bytes` callback, which is
9
+ * exactly a serial port; the shim only supplies the transport (framing,
10
+ * checksums, port settings). Loaded by every build; the C# hosts now only
11
+ * move bytes. Pieces, in load order:
12
+ * timers.js this file -- bmwSleep
13
+ * trace.js busTrace (ifh.trc) and apiTrace (api.trc)
14
+ * framing.js concepts, checksums, frame lengths, port settings
15
+ * exchange.js one request/answer with echo, pacing and retry
16
+ * transport-base.js the Transport contract and the shared serial base
17
+ * native-bus.js the macOS shell's serial bridge
18
+ * web-serial-bus.js Web Serial, with the K-line wake and DTR handling
19
+ * bus.js the one bus instance and the bus lock
20
+ * data-fetch.js JSON / gzip data loaders
21
+ * job-runner.js EDIABAS sessions and webRunJob
22
+ * variant-resolver.js group -> variant over the live bus
23
+ * coding.js the confirmed coding write entry
24
+ * api-router.js the fetch shim that answers /api/* locally
25
+ * install.js window exports and the bus-lock installation
26
+ */
27
+ /* exported bmwSleep */
28
+
29
+ /**
30
+ * Sleep `ms` milliseconds on a clock the browser does not throttle.
31
+ *
32
+ * TIMERS THAT KEEP TIME IN A BACKGROUND TAB. The browser throttles a hidden
33
+ * page's setTimeout to one wake per second (and further after minutes). The K-line
34
+ * exchange holds DTR for the telegram's byte time (a few ms), paces reads in
35
+ * single-digit ms and enforces a 25 ms regeneration gap -- each of which
36
+ * became a full second when the owner's tab was not in front, so a remote
37
+ * helper saw every job take ~1 s while the same tab in front took 150 ms. A
38
+ * dedicated worker's timers are not throttled that way: the bus's waits run
39
+ * there. Falls back to setTimeout where workers are unavailable (node, a
40
+ * blocked blob URL) so nothing else changes.
41
+ *
42
+ * Takes the wait in milliseconds (anything non-numeric or negative is 0)
43
+ * and resolves once it has elapsed.
44
+ *
45
+ * @type {(ms: number) => Promise<void>}
46
+ */
47
+ const bmwSleep = (() => {
48
+ let worker = null;
49
+ let seq = 0;
50
+ const waits = new Map();
51
+ try {
52
+ if (typeof Worker !== 'undefined' && typeof Blob !== 'undefined') {
53
+ const src =
54
+ 'onmessage=(e)=>{const{id,ms}=e.data;setTimeout(()=>postMessage(id),ms)}';
55
+ worker = new Worker(
56
+ URL.createObjectURL(new Blob([src], { type: 'text/javascript' }))
57
+ );
58
+ worker.onmessage = (e) => {
59
+ const r = waits.get(e.data);
60
+ if (r) {
61
+ waits.delete(e.data);
62
+ r();
63
+ }
64
+ };
65
+ worker.onerror = () => {
66
+ worker = null;
67
+ for (const r of waits.values()) r();
68
+ waits.clear();
69
+ };
70
+ }
71
+ } catch {
72
+ worker = null;
73
+ }
74
+ return (ms) =>
75
+ new Promise((r) => {
76
+ const t = Math.max(0, Number(ms) || 0);
77
+ if (!worker) return setTimeout(r, t);
78
+ const id = ++seq;
79
+ waits.set(id, r);
80
+ worker.postMessage({ id, ms: t });
81
+ });
82
+ })();
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @file Wire and API-layer tracing: the in-browser ifh.trc and api.trc.
3
+ *
4
+ * Off by default (zero cost: every call site is behind `busTrace.on`). Turn it
5
+ * on from the console with `busTrace.start()`, run the failing action, then
6
+ * `busTrace.dump()` to print what actually went over the wire. This exists
7
+ * because a transport bug is invisible from the error text alone -- IFH-0003
8
+ * says "the echo was wrong" without ever showing you the echo.
9
+ */
10
+ /* exported busTrace, apiTrace */
11
+
12
+ /** Rows kept while verbose tracing is on (busTrace.start's default). */
13
+ const BUS_TRACE_LIMIT = 400;
14
+ /** Rows kept in the always-on ring buffer of recent wire activity. */
15
+ const BUS_TRACE_RECENT_LIMIT = 60;
16
+ /** Job rows kept by the API-layer trace. */
17
+ const API_TRACE_LIMIT = 500;
18
+
19
+ /**
20
+ * One wire event.
21
+ * @typedef {object} BusTraceRow
22
+ * @property {number} t - Date.now() when it was recorded.
23
+ * @property {string} tag - 'tx', 'rx', 'err' or 'kline'.
24
+ * @property {string} hex - The bytes as upper-case hex pairs ('' when none).
25
+ * @property {number} n - Byte count.
26
+ * @property {string|undefined} note - Free text (timeouts, DTR holds, errors).
27
+ */
28
+
29
+ /**
30
+ * The wire trace (EDIABAS's ifh.trc equivalent): every telegram sent and
31
+ * received, plus K-line control events, with a small always-on ring buffer so
32
+ * an IFH error can print the telegrams that led to it without anyone having
33
+ * run busTrace.start() first.
34
+ */
35
+ const busTrace = {
36
+ on: false,
37
+ /** @type {BusTraceRow[]} */
38
+ rows: [],
39
+ limit: BUS_TRACE_LIMIT,
40
+ // A small ALWAYS-ON ring buffer of the most recent wire activity, kept even
41
+ // when verbose tracing is off (it is cheap -- a handful of {tag,hex,note}
42
+ // objects). When an IFH error surfaces to the user, dumpRecent() prints this
43
+ // so the failing telegrams are on the console without anyone having to have
44
+ // run busTrace.start() first.
45
+ /** @type {BusTraceRow[]} */
46
+ recent: [],
47
+ recentLimit: BUS_TRACE_RECENT_LIMIT,
48
+ /**
49
+ * Start verbose tracing, clearing the previous rows.
50
+ * @param {number} [limit] - Override the row cap for this run.
51
+ * @returns {string} A console-friendly acknowledgement.
52
+ */
53
+ start(limit) {
54
+ this.on = true;
55
+ this.rows = [];
56
+ if (limit) this.limit = limit;
57
+ console.log(
58
+ '[bus] tracing ON — reproduce the failure, then busTrace.dump()'
59
+ );
60
+ return 'tracing';
61
+ },
62
+ /**
63
+ * Stop verbose tracing; the rows stay for dump().
64
+ * @returns {string} A console-friendly acknowledgement.
65
+ */
66
+ stop() {
67
+ this.on = false;
68
+ return `tracing OFF (${this.rows.length} rows kept)`;
69
+ },
70
+ /**
71
+ * Record one wire event into the ring buffer and, while tracing, the rows.
72
+ * @param {string} tag - 'tx', 'rx', 'err' or 'kline'.
73
+ * @param {ArrayLike<number>|null} bytes - The telegram, or null for an event.
74
+ * @param {string} [note] - What happened (timeout, DTR hold, error text).
75
+ */
76
+ add(tag, bytes, note) {
77
+ const row = {
78
+ t: Date.now(),
79
+ tag,
80
+ hex: busTrace.hex(bytes),
81
+ n: bytes ? bytes.length : 0,
82
+ note,
83
+ };
84
+ // ring buffer: always on, bounded, drops the oldest
85
+ this.recent.push(row);
86
+ if (this.recent.length > this.recentLimit) this.recent.shift();
87
+ // verbose buffer: only while explicitly tracing
88
+ if (!this.on) return;
89
+ if (this.rows.length >= this.limit) return;
90
+ this.rows.push(row);
91
+ },
92
+ /**
93
+ * Print the recent ring buffer -- called automatically when an IFH error
94
+ * reaches the UI, or by hand. Labelled so it is obvious it is the auto-dump.
95
+ * @param {string} [why] - The error the dump precedes, for the group label.
96
+ */
97
+ dumpRecent(why) {
98
+ if (!this.recent.length) return;
99
+ const t0 = this.recent[0].t;
100
+ console.groupCollapsed(
101
+ `[bus] wire trace before ${why || 'error'} ` +
102
+ `(${this.recent.length} rows) — expand for telegrams`
103
+ );
104
+ console.table(busTrace.tableRows(this.recent, t0));
105
+ console.groupEnd();
106
+ },
107
+ /**
108
+ * Format bytes as upper-case hex pairs.
109
+ * @param {ArrayLike<number>|null|undefined} b - The bytes.
110
+ * @returns {string} 'AA BB CC', or '' for nothing.
111
+ */
112
+ hex(b) {
113
+ if (!b) return '';
114
+ return Array.from(b, (x) =>
115
+ (x & 0xff).toString(16).padStart(2, '0').toUpperCase()
116
+ ).join(' ');
117
+ },
118
+ /**
119
+ * Shape rows for console.table, with times relative to the first row.
120
+ * @param {BusTraceRow[]} rows - The rows to print.
121
+ * @param {number} t0 - The timestamp the `ms` column counts from.
122
+ * @returns {Array<{ms: number, what: string, len: number, bytes: string, note: string}>}
123
+ */
124
+ tableRows(rows, t0) {
125
+ return rows.map((r) => ({
126
+ ms: r.t - t0,
127
+ what: r.tag,
128
+ len: r.n,
129
+ bytes: r.hex,
130
+ note: r.note || '',
131
+ }));
132
+ },
133
+ /**
134
+ * Print the verbose rows collected since start().
135
+ * @returns {string|undefined} A row count, or nothing when there is no trace.
136
+ */
137
+ dump() {
138
+ if (!this.rows.length) {
139
+ console.log('[bus] nothing traced — busTrace.start() first');
140
+ return;
141
+ }
142
+ const t0 = this.rows[0].t;
143
+ console.table(busTrace.tableRows(this.rows, t0));
144
+ return `${this.rows.length} rows`;
145
+ },
146
+ };
147
+ if (typeof window !== 'undefined') window.busTrace = busTrace;
148
+
149
+ /**
150
+ * One job run as the API layer saw it.
151
+ * @typedef {object} ApiTraceEntry
152
+ * @property {number} t - Date.now() when the job finished.
153
+ * @property {string} sgbd - The SGBD the job ran on.
154
+ * @property {string} job - The job name.
155
+ * @property {string|null} arg - Its argument string.
156
+ * @property {object[]} [sets] - The result sets on success.
157
+ * @property {string} [status] - JOB_STATUS of the first set.
158
+ * @property {string} [error] - The error message on failure.
159
+ */
160
+
161
+ /**
162
+ * The API-LAYER trace: EDIABAS's api.trc equivalent. busTrace is ifh.trc (the
163
+ * raw telegrams); this is the layer above -- one row per job run, with its
164
+ * arguments, its result sets and the JOB_STATUS. Tool32's Trace window shows
165
+ * both layers; the wire tells you WHAT went over the bus, the API layer tells
166
+ * you what the JOB did with it. Recording is gated on `on` (Tool32's trace
167
+ * toggle sets it), matching how EDIABAS only writes the trace when the level
168
+ * is non-zero.
169
+ */
170
+ const apiTrace = {
171
+ on: false,
172
+ /** @type {ApiTraceEntry[]} */
173
+ rows: [],
174
+ limit: API_TRACE_LIMIT,
175
+ /**
176
+ * Start recording job runs.
177
+ * @returns {string} A console-friendly acknowledgement.
178
+ */
179
+ start() {
180
+ this.on = true;
181
+ return 'api trace ON';
182
+ },
183
+ /**
184
+ * Stop recording; the rows stay.
185
+ * @returns {string} A console-friendly acknowledgement.
186
+ */
187
+ stop() {
188
+ this.on = false;
189
+ return 'api trace OFF';
190
+ },
191
+ /** Drop every recorded row. */
192
+ clear() {
193
+ this.rows = [];
194
+ },
195
+ /**
196
+ * Record one job: {sgbd, job, arg, sets, status, error}; the time is added.
197
+ * @param {Omit<ApiTraceEntry, 't'>} entry - The job's outcome.
198
+ */
199
+ add(entry) {
200
+ if (!this.on) return;
201
+ if (this.rows.length >= this.limit) return;
202
+ this.rows.push({ t: Date.now(), ...entry });
203
+ },
204
+ };
205
+ if (typeof window !== 'undefined') window.apiTrace = apiTrace;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @file The Transport contract every bus implements, and the base class the
3
+ * two serial transports share.
4
+ *
5
+ * TWO transports carry the exact same protocol to the car: Web Serial (a
6
+ * K+DCAN cable in a desktop browser) and the native bridge (the macOS shell
7
+ * owning /dev/tty). runExchange, readFrame, withChecksum, frameTotal and
8
+ * verifyChecksum (exchange.js, framing.js) are the protocol and are SHARED by
9
+ * both -- "the protocol does not change with the plumbing." A full merge of
10
+ * the two classes is NOT possible, because two boundaries are physical, not
11
+ * incidental, and each stays a per-transport override:
12
+ *
13
+ * SEAM 1 connect-entry -- Web Serial needs a USER GESTURE for the first
14
+ * requestPort(); the native bridge does not.
15
+ * SEAM 2 K-line line-control -- only Web Serial has setSignals (DTR/break),
16
+ * so fast-init and the ISO 9141 slow-init live there alone; the
17
+ * native bridge has no setSignals.
18
+ *
19
+ * The native correspondence lives in C# (a different runtime, not unified
20
+ * here): SerialProxy.cs is the byte-mover behind NativeSerialBus (open/write/
21
+ * readAvailable/close/flush). Bytes cross that bridge as a JSON int[]
22
+ * (BmacwBridge.cs AsNumberArray), NOT base64 -- base64 corrupted the
23
+ * echo/checksum. src/EdiabasMac is LEGACY (its InpaMac.Api server is deleted);
24
+ * it is reference for what JS reimplemented, not a transport, and is
25
+ * deliberately NOT part of this interface.
26
+ */
27
+ /* exported SerialTransportBase */
28
+
29
+ /**
30
+ * KL30/KL15 as the cable reports them.
31
+ * @typedef {object} BusState
32
+ * @property {number|null} battery - Volts, or null when off/unknown.
33
+ * @property {boolean|null} ignition - KL15, or null when unknown.
34
+ * @property {boolean} [derived] - True when the port cannot report its
35
+ * signals and the nominal "on" was assumed.
36
+ * @property {boolean} [sensed] - True when the value came off a modem line.
37
+ */
38
+
39
+ /**
40
+ * Every transport MUST expose this surface (the rest of the renderer --
41
+ * app.js, coding-write.js, the fetch shim -- calls only these). runExchange
42
+ * also reads/writes sessionConcept, lastResponseAt, inited and initedAddr as
43
+ * shared session state.
44
+ * @typedef {object} Transport
45
+ * @property {boolean} connected - Is the wire up right now.
46
+ * @property {() => Promise<string>} connect - Open the wire (SEAM 1); returns
47
+ * the port label.
48
+ * @property {() => Promise<string|null>} [reconnect] - Silent reopen on load,
49
+ * no gesture (Web Serial only); null when nothing was previously granted.
50
+ * @property {(cfg: PortConfig) => Promise<void>} ensureConfig - Make
51
+ * baud+parity match a concept.
52
+ * @property {(out: ArrayLike<number>, comm: CommParams) => Promise<number[]>} exchange -
53
+ * One request/answer -- SHARED, it just calls runExchange(this, ...).
54
+ * @property {(framed: number[]|null, timeoutMs: number, comm: CommParams) => Promise<number[]>} exchangeRaw -
55
+ * Write+read one frame (SEAM 2); framed null re-reads a continuation.
56
+ * @property {() => Promise<void>} disconnect - Tear the wire down.
57
+ * @property {() => Promise<BusState>} [readState] - KL30/KL15 (absent on
58
+ * the native bridge -- callers guard).
59
+ * @property {() => string} portLabel - A human name for the chip.
60
+ * @property {number|null} sessionConcept - The concept of the last telegram.
61
+ * @property {number|null|undefined} lastResponseAt - Date.now() of the last
62
+ * answer, for ParRegenTime pacing.
63
+ * @property {boolean|null} inited - Whether the K-line wake has run on this
64
+ * port session.
65
+ * @property {number|null} initedAddr - The address the ISO 9141 wake was
66
+ * done for.
67
+ */
68
+
69
+ /**
70
+ * Shared base for the two SERIAL transports (Web Serial + native bridge). They
71
+ * both own a real port whose baud/parity must track the job's concept, and
72
+ * they both keep the same per-session wire state -- so the reconfigure-guard
73
+ * and the state reset live here once.
74
+ */
75
+ class SerialTransportBase {
76
+ /**
77
+ * Clear the full wire state, when the session on the wire ENDS (connect,
78
+ * reconnect, disconnect): a fresh cable has woken nothing and remembers no
79
+ * concept.
80
+ */
81
+ _resetWireState() {
82
+ this.inited = null;
83
+ this.initedAddr = null;
84
+ this.pending = null;
85
+ this.sessionConcept = null;
86
+ }
87
+
88
+ /**
89
+ * Clear the WAKE state only, when the port is REOPENED onto different
90
+ * settings: a reopened port drops the ECU session, so a woken module must
91
+ * be woken again. Deliberately does NOT touch sessionConcept -- runExchange
92
+ * sets that immediately before calling ensureConfig, and the K-line wake in
93
+ * exchangeRaw reads it right after, so clearing it here would blind the
94
+ * wake.
95
+ */
96
+ _resetWakeState() {
97
+ this.inited = null;
98
+ this.initedAddr = null;
99
+ }
100
+
101
+ /**
102
+ * True when a requested config already matches the open port, so
103
+ * ensureConfig can skip the (session-dropping) reopen. Baud and parity are
104
+ * the only settings a concept changes; data/stop bits are constant here.
105
+ * @param {PortConfig} cfg - The settings the next telegram needs.
106
+ * @returns {boolean}
107
+ */
108
+ _configUnchanged(cfg) {
109
+ return (
110
+ !!this.config &&
111
+ this.config.baudRate === cfg.baudRate &&
112
+ this.config.parity === cfg.parity
113
+ );
114
+ }
115
+
116
+ /**
117
+ * One request/answer exchange. IDENTICAL for every transport -- the retry,
118
+ * pacing, response-pending and framing all live in runExchange, which
119
+ * drives the transport through its exchangeRaw/ensureConfig overrides. Kept
120
+ * in the base so there is exactly one copy.
121
+ * @param {ArrayLike<number>} out - The request without its checksum.
122
+ * @param {CommParams} comm - Its wire parameters.
123
+ * @returns {Promise<number[]>} The answer frame.
124
+ */
125
+ async exchange(out, comm) {
126
+ return runExchange(this, out, comm);
127
+ }
128
+ }