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.
- package/LICENSE +674 -0
- package/README.md +351 -0
- package/dist/bmweb.js +2790 -0
- package/package.json +53 -0
- package/runtime/core/bestvm/codec.js +285 -0
- package/runtime/core/bestvm/environment.js +116 -0
- package/runtime/core/bestvm/executor.js +1483 -0
- package/runtime/core/bestvm/index.js +52 -0
- package/runtime/core/bestvm/machine.js +491 -0
- package/runtime/core/bestvm/operands.js +356 -0
- package/runtime/core/bestvm/registers.js +152 -0
- package/runtime/core/bestvm/write-guard.js +111 -0
- package/runtime/core/ipofile/compile.js +364 -0
- package/runtime/core/ipofile/decls.js +187 -0
- package/runtime/core/ipofile/emit.js +708 -0
- package/runtime/core/ipofile/exec.js +164 -0
- package/runtime/core/ipofile/lex.js +243 -0
- package/runtime/core/ipofile/parse.js +550 -0
- package/runtime/core/ipofile/pool.js +404 -0
- package/runtime/core/ipofile/walk.js +431 -0
- package/runtime/core/ipovm/builtin-helpers.js +182 -0
- package/runtime/core/ipovm/builtins-api.js +610 -0
- package/runtime/core/ipovm/builtins-screen.js +493 -0
- package/runtime/core/ipovm/builtins-table.js +166 -0
- package/runtime/core/ipovm/builtins-text.js +166 -0
- package/runtime/core/ipovm/emissions.js +138 -0
- package/runtime/core/ipovm/hosts.js +191 -0
- package/runtime/core/ipovm/operators.js +229 -0
- package/runtime/core/ipovm/structures.js +250 -0
- package/runtime/core/ipovm/suspensions.js +241 -0
- package/runtime/core/ipovm/tape.js +206 -0
- package/runtime/core/ipovm/values.js +241 -0
- package/runtime/core/ipovm/vm.js +1166 -0
- package/runtime/core/translate.js +526 -0
- package/runtime/core/webshim/api-router.js +592 -0
- package/runtime/core/webshim/bus.js +95 -0
- package/runtime/core/webshim/coding.js +82 -0
- package/runtime/core/webshim/data-fetch.js +66 -0
- package/runtime/core/webshim/exchange.js +288 -0
- package/runtime/core/webshim/framing.js +331 -0
- package/runtime/core/webshim/install.js +30 -0
- package/runtime/core/webshim/job-runner.js +319 -0
- package/runtime/core/webshim/native-bus.js +108 -0
- package/runtime/core/webshim/timers.js +82 -0
- package/runtime/core/webshim/trace.js +205 -0
- package/runtime/core/webshim/transport-base.js +128 -0
- package/runtime/core/webshim/variant-resolver.js +249 -0
- package/runtime/core/webshim/web-serial-bus.js +734 -0
- package/runtime/home/bmweb-home.ips +76 -0
- package/runtime/home/bmweb.h +26 -0
- package/runtime/screens/activations.js +258 -0
- package/runtime/screens/garage/diff.js +331 -0
- package/runtime/screens/garage/share.js +276 -0
- package/runtime/screens/garage/store.js +547 -0
- package/runtime/screens/ipo-runtime/cells.js +176 -0
- package/runtime/screens/ipo-runtime/dialogs.js +254 -0
- package/runtime/screens/ipo-runtime/home.js +358 -0
- package/runtime/screens/ipo-runtime/open.js +393 -0
- package/runtime/screens/ipo-runtime/paint-grid.js +106 -0
- package/runtime/screens/ipo-runtime/paint-modern.js +424 -0
- package/runtime/screens/ipo-runtime/print.js +281 -0
- package/runtime/screens/ipo-runtime/program.js +1337 -0
- package/runtime/screens/ipo-runtime/protocol.js +464 -0
- package/runtime/screens/ipo-runtime/script-scan.js +225 -0
- package/runtime/screens/ipo-runtime/translate-sets.js +130 -0
- package/runtime/screens/ipo-runtime/ui.js +249 -0
- package/runtime/screens/ipo-runtime/wire-policy.js +113 -0
- package/runtime/screens/ir.js +324 -0
- package/runtime/screens/search/data.js +153 -0
- package/runtime/screens/search/match.js +285 -0
- package/runtime/screens/search/open.js +66 -0
- 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
|
+
}
|