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,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Group -> variant resolution over the live bus.
|
|
3
|
+
*
|
|
4
|
+
* Which SGBD is this ECU? EDIABAS answers with GROUP files: d_00a4 probes
|
|
5
|
+
* diagnostic address 0xA4, decodes the ident answer, and reports VARIANTE
|
|
6
|
+
* ("MRS4"), which IS the SGBD name to load. The groups in data/groups/ are
|
|
7
|
+
* the same VM bytecode as any job (tools/export/sgbd_export.py); the newer
|
|
8
|
+
* dialect additionally resolves through the t_grtb assignment table
|
|
9
|
+
* (variants.json), reached by tabset/tabsetex "ZuordnungsTabelle".
|
|
10
|
+
* ResolveSgbdFile in the reference engine does exactly this: run the
|
|
11
|
+
* group's IDENTIFIKATION, read result VARIANTE from the first data set.
|
|
12
|
+
*/
|
|
13
|
+
/* exported forgetResolvedVariants, webResolveVariantLast, webResolveVariant */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Why the last webResolveVariant call answered what it did.
|
|
17
|
+
* @typedef {object} ResolveDiag
|
|
18
|
+
* @property {string} group - The group name (lowercased).
|
|
19
|
+
* @property {'no-probe-shipped'|'silent-recently'|'probe-error'|'bus-silent'|'resolved'|'answered-but-unmatched'} path -
|
|
20
|
+
* Which exit the resolver took.
|
|
21
|
+
* @property {string} [variant] - The resolved SGBD name.
|
|
22
|
+
* @property {string} [error] - The probe's error text.
|
|
23
|
+
* @property {number} [empty] - Telegrams that got no usable answer.
|
|
24
|
+
* @property {number} [real] - Telegrams that were answered.
|
|
25
|
+
* @property {number} [sets] - Result sets the probe produced.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* name -> Promise<code|null>; the PROMISE is cached so two concurrent
|
|
30
|
+
* resolves of the same group fetch once.
|
|
31
|
+
* @type {Map<string, Promise<any|null>>}
|
|
32
|
+
*/
|
|
33
|
+
const groupCodeCache = new Map();
|
|
34
|
+
/** @type {Promise<any|null>|null} */
|
|
35
|
+
let groupVariantsPromise = null;
|
|
36
|
+
/**
|
|
37
|
+
* group -> variant. Successful resolutions only, per session: an ECU that
|
|
38
|
+
* did not answer may be a module that was busy, so a re-sweep asks again.
|
|
39
|
+
* @type {Map<string, string>}
|
|
40
|
+
*/
|
|
41
|
+
const groupVariantCache = new Map();
|
|
42
|
+
/**
|
|
43
|
+
* Groups that answered NOTHING, by the time they were last probed. A
|
|
44
|
+
* whole-vehicle script asks each group two or three jobs in a row (INFO,
|
|
45
|
+
* FS_LESEN); an address that is not fitted must not eat a full probe
|
|
46
|
+
* timeout for each of them. Short-lived on purpose: the next module the
|
|
47
|
+
* user plugs in, or the ignition coming on, must be found again.
|
|
48
|
+
* @type {Map<string, number>}
|
|
49
|
+
*/
|
|
50
|
+
const groupMissCache = new Map();
|
|
51
|
+
/** how long a silent address stays silent without re-probing (ms) */
|
|
52
|
+
const GROUP_MISS_TTL_MS = 15000;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load a group's VM bytecode from data/groups/<name>.json.gz, once.
|
|
56
|
+
* @param {string} name - The group name (any case).
|
|
57
|
+
* @returns {Promise<any|null>} The exported program, or null.
|
|
58
|
+
*/
|
|
59
|
+
function loadGroupCode(name) {
|
|
60
|
+
const key = String(name).toLowerCase();
|
|
61
|
+
if (!groupCodeCache.has(key)) {
|
|
62
|
+
groupCodeCache.set(key, webFetchGz(`data/groups/${key}.json.gz`));
|
|
63
|
+
}
|
|
64
|
+
return groupCodeCache.get(key);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Load the t_grtb assignment table (variants.json), once.
|
|
69
|
+
* @returns {Promise<{table?: string, rows: any[]}|null>}
|
|
70
|
+
*/
|
|
71
|
+
function loadGroupVariants() {
|
|
72
|
+
if (!groupVariantsPromise) {
|
|
73
|
+
groupVariantsPromise = webFetchJson('data/groups/variants.json');
|
|
74
|
+
}
|
|
75
|
+
return groupVariantsPromise;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Forget the resolved variants: they are facts about the CAR on the other
|
|
80
|
+
* end of the cable, and the next connection may be a different one.
|
|
81
|
+
*/
|
|
82
|
+
function forgetResolvedVariants() {
|
|
83
|
+
groupVariantCache.clear();
|
|
84
|
+
groupMissCache.clear();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The tables a group probe runs against: the group's OWN tables (tabset
|
|
89
|
+
* dialect: d_0032 reaches its embedded ZuordnungsTabelle copy with a plain
|
|
90
|
+
* `tabset`), plus t_grtb for the `tabsetex "ZuordnungsTabelle", "t_grtb"`
|
|
91
|
+
* dialect. Each side also stands in for the other defensively: exports that
|
|
92
|
+
* predate local tables get the t_grtb master under the local name (its keys
|
|
93
|
+
* embed the address, so the same rows match), and a missing variants.json
|
|
94
|
+
* falls back to the group's local copies as t_grtb -- but where both exist,
|
|
95
|
+
* the real source wins (local copy for tabset, the t_grtb dump for
|
|
96
|
+
* tabsetex), which is what the engine reads in each case.
|
|
97
|
+
* @param {any} code - The group's exported program.
|
|
98
|
+
* @param {{table?: string, rows: any[]}|null} variants - variants.json.
|
|
99
|
+
* @returns {{tables: Record<string, any>, extTables: Record<string, any>}}
|
|
100
|
+
*/
|
|
101
|
+
function groupProbeTables(code, variants) {
|
|
102
|
+
const tables = Object.assign({}, code.tables || {});
|
|
103
|
+
const extTables = { t_grtb: Object.assign({}, code.tables || {}) };
|
|
104
|
+
if (variants && Array.isArray(variants.rows)) {
|
|
105
|
+
const tname = variants.table || 'ZuordnungsTabelle';
|
|
106
|
+
extTables.t_grtb[tname] = variants.rows;
|
|
107
|
+
const hasLocal = Object.keys(tables).some(
|
|
108
|
+
(k) => k.toUpperCase() === tname.toUpperCase()
|
|
109
|
+
);
|
|
110
|
+
if (!hasLocal) tables[tname] = variants.rows;
|
|
111
|
+
}
|
|
112
|
+
return { tables, extTables };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Why the last webResolveVariant call answered null -- the gate screens
|
|
117
|
+
* read this so a failed probe reports WHICH way it failed instead of a
|
|
118
|
+
* generic "no answer". Five exits share that null, and on a live car they
|
|
119
|
+
* mean completely different things (silent bus vs. answered-but-unmatched).
|
|
120
|
+
* @type {ResolveDiag|null}
|
|
121
|
+
*/
|
|
122
|
+
let _lastResolve = null;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The diagnosis of the most recent resolve.
|
|
126
|
+
* @returns {ResolveDiag|null}
|
|
127
|
+
*/
|
|
128
|
+
function webResolveVariantLast() {
|
|
129
|
+
return _lastResolve;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Record how a resolve ended: for webResolveVariantLast, the console, and
|
|
134
|
+
* the session journal (a beta Report must say WHY a module was not
|
|
135
|
+
* identified, not just that inpainit stopped afterwards).
|
|
136
|
+
* @param {string} key - The group name (lowercased).
|
|
137
|
+
* @param {ResolveDiag['path']} path - Which exit was taken.
|
|
138
|
+
* @param {Partial<ResolveDiag>} [extra] - Counters, variant or error text.
|
|
139
|
+
*/
|
|
140
|
+
function noteResolve(key, path, extra) {
|
|
141
|
+
_lastResolve = { group: key, path, ...extra };
|
|
142
|
+
console.info(`[variant] ${key}: ${path}`, extra || '');
|
|
143
|
+
if (typeof Journal !== 'undefined' && Journal.log) {
|
|
144
|
+
Journal.log(
|
|
145
|
+
'variant',
|
|
146
|
+
`${key}: ${path}` + (extra ? ' ' + JSON.stringify(extra) : '')
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve one group to a concrete variant over the live bus: run the
|
|
153
|
+
* group's IDENTIFIKATION exactly the way webRunJob drives a job (passes
|
|
154
|
+
* with memoised answers over webBus.exchange), and return the VARIANTE it
|
|
155
|
+
* reports, LOWERCASED -- that is the SGBD name every loader here expects.
|
|
156
|
+
* IDENTIFIKATION is a read (IDENT is a strong read token), so no
|
|
157
|
+
* allowWrites is involved.
|
|
158
|
+
*
|
|
159
|
+
* A GROUP PROBES SEVERAL PROTOCOLS AT ONE ADDRESS, AND SILENCE ON ONE OF
|
|
160
|
+
* THEM IS A NORMAL STEP, NOT THE END OF THE JOB. d_0012 opens with a DS2
|
|
161
|
+
* frame (12 04 00), then falls back to KWP2000* (B8 12 F1 02 1A 80). An MS45
|
|
162
|
+
* ignores the first and answers the second -- verified against EDIABAS's own
|
|
163
|
+
* ifh.trc on a real E46, which logs SetError EDIABAS_IFH_0009 on the DS2
|
|
164
|
+
* probe and keeps going. The bytecode branches on the answer's LENGTH
|
|
165
|
+
* (slen), so an empty answer is what carries it to the next telegram.
|
|
166
|
+
* Letting that rejection escape returned "no variant" for a DME that was
|
|
167
|
+
* answering perfectly, and the sweep drew it as "not installed" -- hiding
|
|
168
|
+
* ten real stored faults. driveJobOverBus applies the same rule for both.
|
|
169
|
+
* @param {string} groupName - The group (d_xxxx) to probe, any case.
|
|
170
|
+
* @returns {Promise<string|null>} The lowercased SGBD name, or null when
|
|
171
|
+
* nothing answered, the answer matched no variant, or the group could not
|
|
172
|
+
* be loaded; never a made-up name.
|
|
173
|
+
*/
|
|
174
|
+
async function webResolveVariant(groupName) {
|
|
175
|
+
const key = String(groupName).toLowerCase();
|
|
176
|
+
if (groupVariantCache.has(key)) return groupVariantCache.get(key);
|
|
177
|
+
const missedAt = groupMissCache.get(key);
|
|
178
|
+
if (missedAt != null && Date.now() - missedAt < GROUP_MISS_TTL_MS) {
|
|
179
|
+
noteResolve(key, 'silent-recently');
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const code = await loadGroupCode(key);
|
|
183
|
+
if (!code || !code.jobs || code.jobs.IDENTIFIKATION === undefined) {
|
|
184
|
+
noteResolve(key, 'no-probe-shipped');
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const variants = await loadGroupVariants();
|
|
188
|
+
const { tables, extTables } = groupProbeTables(code, variants);
|
|
189
|
+
|
|
190
|
+
let sets;
|
|
191
|
+
const tally = newTally();
|
|
192
|
+
try {
|
|
193
|
+
const r = await driveJobOverBus(
|
|
194
|
+
(send, now) =>
|
|
195
|
+
new Best2Vm(code, {
|
|
196
|
+
tables,
|
|
197
|
+
extTables,
|
|
198
|
+
args: '',
|
|
199
|
+
// Group probing walks diagnostic addresses to find out WHICH module
|
|
200
|
+
// answers, so it must never transmit anything that changes one. It
|
|
201
|
+
// only ever needs idents, and this stays refused on purpose.
|
|
202
|
+
allowWrites: false,
|
|
203
|
+
now,
|
|
204
|
+
send,
|
|
205
|
+
}),
|
|
206
|
+
'IDENTIFIKATION',
|
|
207
|
+
'',
|
|
208
|
+
tally
|
|
209
|
+
);
|
|
210
|
+
sets = r.sets;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
// A job-level failure (bad bytecode, unusable answer) means the address
|
|
213
|
+
// did not identify. Absence of an ANSWER is handled inside the drive.
|
|
214
|
+
noteResolve(key, 'probe-error', {
|
|
215
|
+
error: String((e && e.message) || e),
|
|
216
|
+
empty: tally.empty,
|
|
217
|
+
real: tally.real,
|
|
218
|
+
});
|
|
219
|
+
groupMissCache.set(key, Date.now());
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
// Nothing on this address answered anything: the module is genuinely not
|
|
223
|
+
// there. Distinguished from a resolution that ran but matched no variant,
|
|
224
|
+
// which is a shipped-tables problem rather than a silent bus.
|
|
225
|
+
if (tally.empty && !tally.real) {
|
|
226
|
+
noteResolve(key, 'bus-silent', { empty: tally.empty });
|
|
227
|
+
groupMissCache.set(key, Date.now());
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
for (const s of sets || []) {
|
|
231
|
+
if (typeof s.VARIANTE === 'string' && s.VARIANTE) {
|
|
232
|
+
const v = s.VARIANTE.toLowerCase();
|
|
233
|
+
noteResolve(key, 'resolved', {
|
|
234
|
+
variant: v,
|
|
235
|
+
empty: tally.empty,
|
|
236
|
+
real: tally.real,
|
|
237
|
+
});
|
|
238
|
+
groupVariantCache.set(key, v);
|
|
239
|
+
return v;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
noteResolve(key, 'answered-but-unmatched', {
|
|
243
|
+
empty: tally.empty,
|
|
244
|
+
real: tally.real,
|
|
245
|
+
sets: (sets || []).length,
|
|
246
|
+
});
|
|
247
|
+
groupMissCache.set(key, Date.now());
|
|
248
|
+
return null;
|
|
249
|
+
}
|