driverge-mcp 0.0.0 → 0.0.1

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 (81) hide show
  1. package/README.md +275 -29
  2. package/dist/codegen/esp32.d.ts +11 -3
  3. package/dist/codegen/esp32.js +221 -10
  4. package/dist/codegen/esp32.js.map +1 -1
  5. package/dist/codegen/ident.d.ts +24 -2
  6. package/dist/codegen/ident.js +37 -4
  7. package/dist/codegen/ident.js.map +1 -1
  8. package/dist/codegen/index.d.ts +12 -7
  9. package/dist/codegen/index.js +17 -14
  10. package/dist/codegen/index.js.map +1 -1
  11. package/dist/codegen/lint.js +28 -8
  12. package/dist/codegen/lint.js.map +1 -1
  13. package/dist/codegen/portable-cpp.d.ts +8 -0
  14. package/dist/codegen/portable-cpp.js +281 -0
  15. package/dist/codegen/portable-cpp.js.map +1 -0
  16. package/dist/codegen/portable.d.ts +99 -3
  17. package/dist/codegen/portable.js +419 -99
  18. package/dist/codegen/portable.js.map +1 -1
  19. package/dist/codegen/stm32.d.ts +11 -3
  20. package/dist/codegen/stm32.js +156 -11
  21. package/dist/codegen/stm32.js.map +1 -1
  22. package/dist/codegen/types.d.ts +32 -0
  23. package/dist/codegen/types.js +27 -0
  24. package/dist/codegen/types.js.map +1 -1
  25. package/dist/mcp/cache.d.ts +15 -2
  26. package/dist/mcp/cache.js +32 -4
  27. package/dist/mcp/cache.js.map +1 -1
  28. package/dist/mcp/register.d.ts +17 -0
  29. package/dist/mcp/register.js +149 -21
  30. package/dist/mcp/register.js.map +1 -1
  31. package/dist/mcp/summary.js +8 -0
  32. package/dist/mcp/summary.js.map +1 -1
  33. package/dist/pdf/address.d.ts +4 -0
  34. package/dist/pdf/address.js +28 -0
  35. package/dist/pdf/address.js.map +1 -0
  36. package/dist/pdf/command.js +87 -14
  37. package/dist/pdf/command.js.map +1 -1
  38. package/dist/pdf/extract.js +23 -2
  39. package/dist/pdf/extract.js.map +1 -1
  40. package/dist/pdf/generic-register-table.d.ts +5 -0
  41. package/dist/pdf/generic-register-table.js +125 -0
  42. package/dist/pdf/generic-register-table.js.map +1 -0
  43. package/dist/pdf/index.d.ts +2 -0
  44. package/dist/pdf/index.js +2 -0
  45. package/dist/pdf/index.js.map +1 -1
  46. package/dist/pdf/interface-kind.d.ts +11 -0
  47. package/dist/pdf/interface-kind.js +18 -0
  48. package/dist/pdf/interface-kind.js.map +1 -1
  49. package/dist/pdf/manufacturer.js +39 -0
  50. package/dist/pdf/manufacturer.js.map +1 -1
  51. package/dist/pdf/maxim-register-map.d.ts +11 -0
  52. package/dist/pdf/maxim-register-map.js +544 -0
  53. package/dist/pdf/maxim-register-map.js.map +1 -0
  54. package/dist/pdf/part.js +8 -0
  55. package/dist/pdf/part.js.map +1 -1
  56. package/dist/pdf/prose-commands.d.ts +4 -0
  57. package/dist/pdf/prose-commands.js +90 -0
  58. package/dist/pdf/prose-commands.js.map +1 -0
  59. package/dist/pdf/st-bit-layout.d.ts +3 -0
  60. package/dist/pdf/st-bit-layout.js +238 -0
  61. package/dist/pdf/st-bit-layout.js.map +1 -0
  62. package/dist/pdf/ti-field-descriptions.d.ts +10 -0
  63. package/dist/pdf/ti-field-descriptions.js +87 -0
  64. package/dist/pdf/ti-field-descriptions.js.map +1 -0
  65. package/dist/pdf/ti-register-map.d.ts +5 -0
  66. package/dist/pdf/ti-register-map.js +84 -0
  67. package/dist/pdf/ti-register-map.js.map +1 -0
  68. package/dist/pdf/types.d.ts +15 -3
  69. package/dist/pdf/types.js +4 -1
  70. package/dist/pdf/types.js.map +1 -1
  71. package/dist/schema/assemble.d.ts +35 -3
  72. package/dist/schema/assemble.js +86 -13
  73. package/dist/schema/assemble.js.map +1 -1
  74. package/dist/schema/types.d.ts +21 -1
  75. package/dist/schema/validate.js +37 -10
  76. package/dist/schema/validate.js.map +1 -1
  77. package/dist/server.d.ts +18 -0
  78. package/dist/server.js +49 -3
  79. package/dist/server.js.map +1 -1
  80. package/package.json +1 -1
  81. package/schemas/datasheet.schema.json +17 -3
@@ -1,15 +1,31 @@
1
1
  // Portable thin-HAL codegen. Renders a deterministic C driver skeleton from the
2
2
  // frozen DatasheetJson: register/command constants, bit-field mask+shift macros,
3
- // the 5-function thin-HAL seam (and NOTHING platform-specific see
4
- // thin-hal-non-negotiable), function stubs, and TODO(driverge) markers at every
5
- // spot needing host-AI reasoning. Output is reproducible: no timestamps, stable
6
- // ordering. The host AI fills the brief and re-validates via validate_driver.
7
- import { HEX_ONLY, fieldMask, hex2, macro, slug } from "./ident.js";
8
- const AUTOGEN = (part, mfr, busLine) => [
3
+ // the per-bus thin-HAL seam (I2C: hal_i2c_read/write + hal_delay_ms; SPI: one
4
+ // combined hal_spi_transfer + hal_delay_ms see BUS_SEAM below and
5
+ // thin-hal-non-negotiable) and NOTHING platform-specific, function stubs, and
6
+ // TODO(driverge) markers at every spot needing host-AI reasoning. Output is
7
+ // reproducible: no timestamps, stable ordering. The host AI fills the brief and
8
+ // re-validates via validate_driver.
9
+ import { registerWidth } from "../pdf/types.js";
10
+ import { HEX_ONLY, commentSafe, fieldMask, macro, maskHex, slug } from "./ident.js";
11
+ // Exported: the "cpp" language flavor (see portable-cpp.ts) renders the SAME
12
+ // #define macro / TODO blocks and thin-HAL seam declarations as this C
13
+ // generator — reusing these pure helpers instead of re-deriving them is how the
14
+ // two flavors stay byte-for-byte consistent on everything except the
15
+ // struct-vs-class/free-function-vs-method shape (see Session D: cpp language
16
+ // option).
17
+ // part/mfr/busLine are free text lifted verbatim from the parsed datasheet
18
+ // (metadata.part/manufacturer; busLine's only free-text ingredient is the I2C
19
+ // address, which is hex-validated, but commentSafe-ing the whole trio here —
20
+ // once, in the one place every AUTOGEN caller shares — is simpler and safer
21
+ // than re-deriving which piece is "safe enough" at each call site) — see B2:
22
+ // comment-escape injection. commentSafe neutralizes any embedded `*/`/`/*` so
23
+ // none of the three can ever prematurely close (or spoof) this block comment.
24
+ export const AUTOGEN = (part, mfr, busLine) => [
9
25
  "/*",
10
- ` * ${part} driver — portable thin-HAL skeleton (generated by Driverge).`,
11
- ` * Manufacturer: ${mfr}`,
12
- ` * ${busLine}`,
26
+ ` * ${commentSafe(part)} driver — portable thin-HAL skeleton (generated by Driverge).`,
27
+ ` * Manufacturer: ${commentSafe(mfr)}`,
28
+ ` * ${commentSafe(busLine)}`,
13
29
  " *",
14
30
  " * This is a deterministic skeleton. Every TODO(driverge) marks a spot the",
15
31
  " * host AI must complete (init sequence, vendor quirks, docs) using the",
@@ -22,7 +38,7 @@ const AUTOGEN = (part, mfr, busLine) => [
22
38
  // ---------------------------------------------------------------------------
23
39
  // register_map
24
40
  // ---------------------------------------------------------------------------
25
- function registerConstants(prefix, registers) {
41
+ export function registerConstants(prefix, registers) {
26
42
  const lines = ["/* Register addresses. */"];
27
43
  const seen = new Set();
28
44
  for (const r of registers) {
@@ -36,28 +52,178 @@ function registerConstants(prefix, registers) {
36
52
  }
37
53
  return lines;
38
54
  }
39
- function bitFieldMacros(prefix, registers) {
55
+ /** Where the host AI must enumerate a register map that Driverge couldn't extract. */
56
+ export function registerMapTodo(prefix, pages) {
57
+ const where = pages.length
58
+ ? ` (register section detected on page(s) ${pages.join(", ")})`
59
+ : "";
60
+ return [
61
+ "/* Register map. */",
62
+ "/* TODO(driverge): Driverge could not auto-extract this device's register map",
63
+ ` * from the datasheet${where}. Enumerate the registers from the datasheet (read`,
64
+ " * the driverge://datasheet resource) and add one line per register:",
65
+ ` * #define ${prefix}_REG_<NAME> 0x..`,
66
+ ` * plus ${prefix}_<REG>_<FIELD>_MASK / _SHIFT macros for any bit fields. */`,
67
+ ];
68
+ }
69
+ /** Where the host AI must enumerate a command set that Driverge couldn't extract. */
70
+ export function commandSetTodo(prefix, pages) {
71
+ const where = pages.length
72
+ ? ` (command section detected on page(s) ${pages.join(", ")})`
73
+ : "";
74
+ return [
75
+ "/* Command codes. */",
76
+ "/* TODO(driverge): Driverge could not auto-extract this device's command set",
77
+ ` * from the datasheet${where}. Enumerate the commands from the datasheet (read`,
78
+ " * the driverge://datasheet resource) and add one line per command:",
79
+ ` * #define ${prefix}_CMD_<NAME> 0x.. */`,
80
+ ];
81
+ }
82
+ /** fill_in_brief.register_map_todo text — shared by the C and cpp generators. */
83
+ export function registerMapTodoText(json, name, prefix) {
84
+ return `Enumerate ${json.metadata.part || name}'s register map from the datasheet — Driverge could not auto-extract it. Add a "#define ${prefix}_REG_<NAME> 0x.." for each register address (and ${prefix}_<REG>_<FIELD>_MASK/_SHIFT for bit fields) using the driverge://datasheet resource, then wire the read/write helpers accordingly.`;
85
+ }
86
+ /** fill_in_brief.command_set_todo text — shared by the C and cpp generators. */
87
+ export function commandSetTodoText(json, name, prefix) {
88
+ return `Enumerate ${json.metadata.part || name}'s command set from the datasheet — Driverge could not auto-extract it. Add a "#define ${prefix}_CMD_<NAME> 0x.." for each command using the driverge://datasheet resource.`;
89
+ }
90
+ export const BUS_SEAM = {
91
+ I2C: {
92
+ decl: [
93
+ "void hal_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
94
+ "void hal_i2c_read (uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
95
+ ],
96
+ handleField: " uint8_t i2c_addr;",
97
+ readBody: [" hal_i2c_read(dev->i2c_addr, reg, value, 1);"],
98
+ writeBody: [" hal_i2c_write(dev->i2c_addr, reg, &value, 1);"],
99
+ },
100
+ SPI: {
101
+ decl: [
102
+ "/* One call = one CS-framed transaction: CS is asserted, tx_len bytes are",
103
+ " * clocked out, then rx_len bytes are clocked in, then CS is deasserted.",
104
+ " * tx/rx may be NULL when their respective length is 0. CS is handled",
105
+ " * entirely inside hal_spi_transfer — callers never touch it. */",
106
+ "void hal_spi_transfer(const uint8_t *tx, uint16_t tx_len, uint8_t *rx, uint16_t rx_len);",
107
+ ],
108
+ handleField: " uint8_t _reserved; /* platform CS handled in hal_spi_transfer */",
109
+ readBody: [" hal_spi_transfer(&reg, 1, value, 1);"],
110
+ writeBody: [
111
+ " uint8_t frame[2];",
112
+ " frame[0] = reg;",
113
+ " frame[1] = value;",
114
+ " hal_spi_transfer(frame, 2, NULL, 0);",
115
+ ],
116
+ },
117
+ UART: {
118
+ decl: [
119
+ "/* hal_uart_write is a blocking write of len bytes. */",
120
+ "/* hal_uart_read reads up to len bytes and returns the count actually read",
121
+ " * within timeout_ms. */",
122
+ "void hal_uart_write(const uint8_t *data, uint16_t len);",
123
+ "uint16_t hal_uart_read(uint8_t *data, uint16_t len, uint32_t timeout_ms);",
124
+ ],
125
+ handleField: " uint8_t _reserved; /* platform UART handle is bound inside the hal_uart_* implementation */",
126
+ readBody: [],
127
+ writeBody: [],
128
+ },
129
+ // Session C — CAN. Unlike I2C's two-function read/write pair or SPI's single
130
+ // transfer, one hal_can_transfer() call both sends a frame AND (optionally)
131
+ // waits for the one response frame it provokes — CAN has no separate
132
+ // read/write primitive, only "put a frame on the bus, addressed by
133
+ // arbitration id, and maybe get one back". rx_len is in/out (capacity in,
134
+ // actual byte count out) because a CAN payload is 0-8 bytes, not a fixed
135
+ // size.
136
+ CAN: {
137
+ decl: [
138
+ "/* A call sends one CAN frame to arbitration id `id` and optionally waits",
139
+ " * for one response frame (pass rx/rx_len NULL/0 to only transmit).",
140
+ " * Returns 0 on success. rx_len is in/out: in = buffer capacity, out =",
141
+ " * bytes actually received. */",
142
+ "int hal_can_transfer(uint32_t id, const uint8_t *tx, uint8_t tx_len, uint8_t *rx, uint8_t *rx_len, uint32_t timeout_ms);",
143
+ ],
144
+ handleField: " uint8_t _reserved; /* platform CAN peripheral/arbitration-id binding is handled inside the hal_can_transfer implementation */",
145
+ readBody: [],
146
+ writeBody: [],
147
+ },
148
+ };
149
+ /** Which BUS_SEAM row a protocol.bus resolves to. "unknown" defaults to the I2C
150
+ * seam, matching the pre-existing (pre-Session-B) fallback behavior. */
151
+ export function seamBusFor(bus) {
152
+ if (bus === "SPI")
153
+ return "SPI";
154
+ if (bus === "UART")
155
+ return "UART";
156
+ if (bus === "CAN")
157
+ return "CAN";
158
+ return "I2C";
159
+ }
160
+ // UART and CAN have no universal register/command framing (start bytes, IDs,
161
+ // checksums, arbitration ids are all device-specific — see decisions:
162
+ // thin-hal-non-negotiable), so rather than fabricate a wire format, the
163
+ // generated body is a deliberate TODO(driverge) reasoning gap over the raw
164
+ // seam call(s): a compilable placeholder (params voided, returns -1) that
165
+ // names the seam function(s) so the host AI knows exactly what to build on
166
+ // top of. See fill_in_brief.framing_todo.
167
+ export function uartFramingBody(params) {
168
+ return [
169
+ " /* TODO(driverge): this device has no universal UART register/command",
170
+ " * framing — build the request frame and call hal_uart_write(), then",
171
+ " * parse the response via hal_uart_read(). See fill_in_brief.framing_todo. */",
172
+ ...params.map((p) => ` (void)${p};`),
173
+ " return -1;",
174
+ ];
175
+ }
176
+ export function canFramingBody(params) {
177
+ return [
178
+ " /* TODO(driverge): this device has no universal CAN register/command",
179
+ " * framing — build the CAN frame and call hal_can_transfer() with this",
180
+ " * part's arbitration id to send it (and receive any response). See",
181
+ " * fill_in_brief.framing_todo. */",
182
+ ...params.map((p) => ` (void)${p};`),
183
+ " return -1;",
184
+ ];
185
+ }
186
+ // CAN-specific reasoning gap shared by both interface kinds: which arbitration
187
+ // id / SDO index / message ID maps to which register or command is
188
+ // device-specific and belongs in fill_in_brief.quirks_todo, NOT a generic
189
+ // checksum note reused from UART.
190
+ export const CAN_QUIRKS_NOTE = "Confirm the arbitration IDs / SDO message schema this part uses for register/command access from the datasheet; CAN device interfaces are device-specific.";
191
+ export function bitFieldMacros(prefix, registers) {
40
192
  const lines = [];
41
193
  for (const r of registers) {
42
194
  if (r.bitFields.length === 0)
43
195
  continue;
44
- lines.push("", `/* ${r.name} bit fields. */`);
196
+ const width = registerWidth(r);
197
+ const widthNote = width > 8 ? ` (${width}-bit register)` : "";
198
+ // r.name is free text lifted verbatim from the parsed datasheet — see B2:
199
+ // comment-escape injection — so it must not be allowed to close this
200
+ // block comment early via an embedded `*/`.
201
+ lines.push("", `/* ${commentSafe(r.name)} bit fields${widthNote}. */`);
45
202
  for (const f of r.bitFields) {
46
203
  const base = `${prefix}_${macro(r.name)}_${macro(f.name)}`;
47
- lines.push(`#define ${base}_MASK ${hex2(fieldMask(f.msb, f.lsb))}`);
204
+ lines.push(`#define ${base}_MASK ${maskHex(fieldMask(f.msb, f.lsb, width), width)}`);
48
205
  lines.push(`#define ${base}_SHIFT ${f.lsb}`);
49
206
  }
50
207
  }
51
208
  return lines;
52
209
  }
53
- function registerDriver(json, name, prefix) {
54
- const registers = json.interface.registers;
55
- const spi = json.protocol.bus === "SPI";
210
+ function registerDriver(json, registers, name, prefix) {
211
+ const busKind = seamBusFor(json.protocol.bus);
212
+ const spi = busKind === "SPI";
213
+ const uart = busKind === "UART";
214
+ const can = busKind === "CAN";
215
+ const seam = BUS_SEAM[busKind];
56
216
  const addr = json.protocol.addresses?.[0];
57
217
  const guard = `${prefix}_H`;
58
218
  const type = `${name}_t`;
59
219
  const header = [
60
- AUTOGEN(json.metadata.part || name, json.metadata.manufacturer, spi ? "Bus: SPI" : `Bus: I2C, address ${addr ?? "(unknown — see TODO)"}`),
220
+ AUTOGEN(json.metadata.part || name, json.metadata.manufacturer, spi
221
+ ? "Bus: SPI"
222
+ : uart
223
+ ? "Bus: UART"
224
+ : can
225
+ ? "Bus: CAN"
226
+ : `Bus: I2C, address ${addr ?? "(unknown — see TODO)"}`),
61
227
  "",
62
228
  `#ifndef ${guard}`,
63
229
  `#define ${guard}`,
@@ -70,7 +236,7 @@ function registerDriver(json, name, prefix) {
70
236
  "#endif",
71
237
  "",
72
238
  ];
73
- if (!spi) {
239
+ if (!spi && !uart && !can) {
74
240
  if (addr) {
75
241
  header.push("/* I2C device address. */", `#define ${prefix}_I2C_ADDR ${addr}`, "");
76
242
  }
@@ -78,25 +244,28 @@ function registerDriver(json, name, prefix) {
78
244
  header.push("/* TODO(driverge): set the I2C device address (not found in the datasheet parse). */", `#define ${prefix}_I2C_ADDR 0x00`, "");
79
245
  }
80
246
  }
81
- header.push(...registerConstants(prefix, registers));
247
+ const regLines = registerConstants(prefix, registers);
248
+ const hasRegs = regLines.some((l) => l.startsWith("#define"));
249
+ header.push(...(hasRegs
250
+ ? regLines
251
+ : registerMapTodo(prefix, json.extraction?.detectedPages ?? [])));
252
+ if (registers.some((r) => registerWidth(r) > 8)) {
253
+ header.push("", "/* NOTE: some registers here are wider than 8 bits (see the per-register", " * annotations). Bit-field masks are register-width correct; multi-byte access", " * framing (byte order / transfer size) is device-specific and belongs on the", " * hal_* seam. */");
254
+ }
82
255
  const bits = bitFieldMacros(prefix, registers);
83
256
  if (bits.length > 0)
84
257
  header.push("", "/* Bit-field mask/shift accessors. */", ...bits);
85
- header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */", ...(spi
86
- ? [
87
- "void hal_spi_write(uint8_t *data, uint16_t len);",
88
- "void hal_spi_read (uint8_t *data, uint16_t len);",
89
- ]
90
- : [
91
- "void hal_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
92
- "void hal_i2c_read (uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
93
- ]), "void hal_delay_ms (uint32_t ms);", "", "/* Driver handle. */", `typedef struct {`, spi ? " uint8_t _reserved; /* platform CS handled in hal_spi_* */" : " uint8_t i2c_addr;", `} ${type};`, "", "/* Public API. */", `int ${name}_init(${type} *dev);`, `int ${name}_read_register(${type} *dev, uint8_t reg, uint8_t *value);`, `int ${name}_write_register(${type} *dev, uint8_t reg, uint8_t value);`, "", '#ifdef __cplusplus', "}", "#endif", `#endif /* ${guard} */`, "");
94
- const readBody = spi
95
- ? [" hal_spi_write(&reg, 1);", " hal_spi_read(value, 1);"]
96
- : [" hal_i2c_read(dev->i2c_addr, reg, value, 1);"];
97
- const writeBody = spi
98
- ? [" uint8_t frame[2];", " frame[0] = reg;", " frame[1] = value;", " hal_spi_write(frame, 2);"]
99
- : [" hal_i2c_write(dev->i2c_addr, reg, &value, 1);"];
258
+ header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */", ...seam.decl, "void hal_delay_ms (uint32_t ms);", "", "/* Driver handle. */", `typedef struct {`, seam.handleField, `} ${type};`, "", "/* Public API. */", `int ${name}_init(${type} *dev);`, `int ${name}_read_register(${type} *dev, uint8_t reg, uint8_t *value);`, `int ${name}_write_register(${type} *dev, uint8_t reg, uint8_t value);`, "", '#ifdef __cplusplus', "}", "#endif", `#endif /* ${guard} */`, "");
259
+ const readBody = uart
260
+ ? uartFramingBody(["dev", "reg", "value"])
261
+ : can
262
+ ? canFramingBody(["dev", "reg", "value"])
263
+ : [" if (dev == NULL || value == NULL) {", " return -1;", " }", ...seam.readBody, " return 0;"];
264
+ const writeBody = uart
265
+ ? uartFramingBody(["dev", "reg", "value"])
266
+ : can
267
+ ? canFramingBody(["dev", "reg", "value"])
268
+ : [" if (dev == NULL) {", " return -1;", " }", ...seam.writeBody, " return 0;"];
100
269
  const source = [
101
270
  `#include "${name}.h"`,
102
271
  "",
@@ -104,55 +273,79 @@ function registerDriver(json, name, prefix) {
104
273
  " if (dev == NULL) {",
105
274
  " return -1;",
106
275
  " }",
107
- ...(spi ? [] : [` dev->i2c_addr = ${prefix}_I2C_ADDR;`]),
108
- " /* TODO(driverge): implement the power-on / reset init sequence — the",
109
- " * correct register write order and values, plus any required startup",
110
- ` * delay. See fill_in_brief.init_sequence_todo. Use ${name}_write_register()`,
111
- " * and hal_delay_ms(). */",
276
+ ...(spi || uart || can ? [] : [` dev->i2c_addr = ${prefix}_I2C_ADDR;`]),
277
+ ...registerInitTodo(uart, can, `${name}_write_register()`),
112
278
  " return 0;",
113
279
  "}",
114
280
  "",
115
281
  `int ${name}_read_register(${type} *dev, uint8_t reg, uint8_t *value) {`,
116
- " if (dev == NULL || value == NULL) {",
117
- " return -1;",
118
- " }",
119
282
  ...readBody,
120
- " return 0;",
121
283
  "}",
122
284
  "",
123
285
  `int ${name}_write_register(${type} *dev, uint8_t reg, uint8_t value) {`,
124
- " if (dev == NULL) {",
125
- " return -1;",
126
- " }",
127
286
  ...writeBody,
128
- " return 0;",
129
287
  "}",
130
288
  "",
131
289
  ];
290
+ const brief = registerBrief(json);
291
+ if (!hasRegs) {
292
+ brief.register_map_todo = registerMapTodoText(json, name, prefix);
293
+ }
132
294
  return {
133
295
  files: makeFiles(name, header, source),
134
- fill_in_brief: registerBrief(json),
296
+ fill_in_brief: brief,
135
297
  };
136
298
  }
137
- function registerBrief(json) {
299
+ export function registerBrief(json, calls) {
138
300
  const part = json.metadata.part || "the device";
139
- return {
301
+ const name = slug(part);
302
+ const c = {
303
+ init: calls?.init ?? `${name}_init`,
304
+ readRegister: calls?.readRegister ?? "read_register",
305
+ writeRegister: calls?.writeRegister ?? "write_register",
306
+ };
307
+ const quirks = [
308
+ `Capture vendor timing/behavior quirks for ${part} (e.g. mandatory power-on delay, soft-reset requirement, read-back constraints) and enforce them in ${c.init} using hal_delay_ms.`,
309
+ ];
310
+ if (json.protocol.bus === "SPI") {
311
+ quirks.push(`Verify the register address bit convention: many SPI chips set the address MSB for reads (e.g. 0x80 | reg) — check the datasheet's SPI framing and adjust ${c.readRegister}/${c.writeRegister}.`);
312
+ }
313
+ if (json.protocol.bus === "UART") {
314
+ quirks.push("Verify the response checksum algorithm from the datasheet before trusting frame data — vendor checksum/CRC methods for UART command-and-response parts vary.");
315
+ }
316
+ if (json.protocol.bus === "CAN") {
317
+ quirks.push(CAN_QUIRKS_NOTE);
318
+ }
319
+ const brief = {
140
320
  init_sequence_todo: `Determine the correct power-on/reset init sequence for ${part}: which registers to write, in what order, with what values (mode/config), and any required startup delay. Read the datasheet init/operation section via the driverge://datasheet resource.`,
141
- quirks_todo: `Capture vendor timing/behavior quirks for ${part} (e.g. mandatory power-on delay, soft-reset requirement, read-back constraints) and enforce them in ${slug(part)}_init using hal_delay_ms.`,
321
+ quirks_todo: quirks.join(" "),
142
322
  doc_todo: "Add Doxygen comments for all public functions and any device-specific compensation/conversion formulas.",
143
323
  };
324
+ if (json.protocol.bus === "UART") {
325
+ brief.framing_todo = `Implement ${name}'s UART frame protocol in ${c.readRegister}/${c.writeRegister}: build request frames with hal_uart_write and parse responses with hal_uart_read, per the datasheet's communication/frame-format section.`;
326
+ }
327
+ if (json.protocol.bus === "CAN") {
328
+ brief.framing_todo = `Implement ${name}'s CAN frame protocol in ${c.readRegister}/${c.writeRegister}: build request frames and call hal_can_transfer() with this part's arbitration id(s), per the datasheet's CAN communication/message-ID section.`;
329
+ }
330
+ return brief;
144
331
  }
145
332
  // ---------------------------------------------------------------------------
146
333
  // command_set
147
334
  // ---------------------------------------------------------------------------
148
- function commandDriver(json, name, prefix) {
149
- const commands = json.interface.commands;
335
+ function commandDriver(json, commands, name, prefix) {
336
+ const uart = json.protocol.bus === "UART";
337
+ const can = json.protocol.bus === "CAN";
338
+ const noAddrBus = uart || can; // neither UART nor CAN has a bus device address
150
339
  const addr = json.protocol.addresses?.[0];
151
340
  const crc = commands.find((c) => c.crc)?.crc;
152
341
  const guard = `${prefix}_H`;
153
342
  const type = `${name}_t`;
154
343
  const header = [
155
- AUTOGEN(json.metadata.part || name, json.metadata.manufacturer, `Bus: I2C, address ${addr ?? "(unknown — see TODO)"} · command-set device`),
344
+ AUTOGEN(json.metadata.part || name, json.metadata.manufacturer, uart
345
+ ? "Bus: UART · command-set device"
346
+ : can
347
+ ? "Bus: CAN · command-set device"
348
+ : `Bus: I2C, address ${addr ?? "(unknown — see TODO)"} · command-set device`),
156
349
  "",
157
350
  `#ifndef ${guard}`,
158
351
  `#define ${guard}`,
@@ -165,25 +358,48 @@ function commandDriver(json, name, prefix) {
165
358
  "#endif",
166
359
  "",
167
360
  ];
168
- if (addr) {
361
+ if (noAddrBus) {
362
+ // No bus device address on UART/CAN — only I2C command-set parts get an
363
+ // *_I2C_ADDR macro (see fill_in_brief.framing_todo for the UART/CAN
364
+ // reasoning gap this leaves instead).
365
+ }
366
+ else if (addr) {
169
367
  header.push("/* I2C device address. */", `#define ${prefix}_I2C_ADDR ${addr}`, "");
170
368
  }
171
369
  else {
172
370
  header.push("/* TODO(driverge): set the I2C device address. */", `#define ${prefix}_I2C_ADDR 0x00`, "");
173
371
  }
174
- header.push("/* Command codes. */");
175
- const seen = new Set();
176
- for (const c of commands) {
177
- const macroName = `${prefix}_CMD_${macro(c.name)}`;
178
- if (seen.has(macroName))
179
- continue;
180
- seen.add(macroName);
181
- header.push(`#define ${macroName} ${c.code.toUpperCase().replace("0X", "0x")}`);
372
+ if (commands.length === 0) {
373
+ header.push(...commandSetTodo(prefix, json.extraction?.detectedPages ?? []));
374
+ }
375
+ else {
376
+ header.push("/* Command codes. */");
377
+ const seen = new Set();
378
+ for (const c of commands) {
379
+ const macroName = `${prefix}_CMD_${macro(c.name)}`;
380
+ if (seen.has(macroName))
381
+ continue;
382
+ seen.add(macroName);
383
+ const line = `#define ${macroName} ${c.code.toUpperCase().replace("0X", "0x")}`;
384
+ // c.params is currently always hex-derived (prose-commands.ts) and thus
385
+ // safe as-is, but the Command type doesn't guarantee that — commentSafe
386
+ // defensively, same reasoning as AUTOGEN/bitFieldMacros above (B2).
387
+ header.push(c.params && c.params.length > 0
388
+ ? `${line} /* params: ${commentSafe(c.params.join(", "))} */`
389
+ : line);
390
+ }
182
391
  }
183
392
  if (crc) {
184
393
  header.push("", `/* CRC-${crc.width} checksum parameters. */`, `#define ${prefix}_CRC_POLY ${crc.poly}`, `#define ${prefix}_CRC_INIT ${crc.init}`);
185
394
  }
186
- header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */", "void hal_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);", "void hal_i2c_read (uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);", "void hal_delay_ms (uint32_t ms);", "", "/* Driver handle. */", "typedef struct {", " uint8_t i2c_addr;", `} ${type};`, "", "/* Public API. */", `int ${name}_init(${type} *dev);`, `int ${name}_send_command(${type} *dev, uint16_t command);`, `int ${name}_read_data(${type} *dev, uint8_t *buffer, uint16_t len);`, ...(crc ? [`uint8_t ${name}_crc8(const uint8_t *data, uint16_t len);`] : []), "", '#ifdef __cplusplus', "}", "#endif", `#endif /* ${guard} */`, "");
395
+ header.push("", "/* Thin-HAL seam — implement these for your platform (see thin-hal). */", ...(uart
396
+ ? BUS_SEAM.UART.decl
397
+ : can
398
+ ? BUS_SEAM.CAN.decl
399
+ : [
400
+ "void hal_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
401
+ "void hal_i2c_read (uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);",
402
+ ]), "void hal_delay_ms (uint32_t ms);", "", "/* Driver handle. */", "typedef struct {", uart ? BUS_SEAM.UART.handleField : can ? BUS_SEAM.CAN.handleField : " uint8_t i2c_addr;", `} ${type};`, "", "/* Public API. */", `int ${name}_init(${type} *dev);`, `int ${name}_send_command(${type} *dev, uint16_t command);`, `int ${name}_read_data(${type} *dev, uint8_t *buffer, uint16_t len);`, ...(crc ? [`uint8_t ${name}_crc8(const uint8_t *data, uint16_t len);`] : []), "", '#ifdef __cplusplus', "}", "#endif", `#endif /* ${guard} */`, "");
187
403
  const source = [
188
404
  `#include "${name}.h"`,
189
405
  "",
@@ -191,68 +407,172 @@ function commandDriver(json, name, prefix) {
191
407
  " if (dev == NULL) {",
192
408
  " return -1;",
193
409
  " }",
194
- ` dev->i2c_addr = ${prefix}_I2C_ADDR;`,
195
- " /* TODO(driverge): power-up / soft-reset sequence and startup delay. See",
196
- " * fill_in_brief.init_sequence_todo. */",
410
+ ...(noAddrBus ? [] : [` dev->i2c_addr = ${prefix}_I2C_ADDR;`]),
411
+ ...COMMAND_INIT_TODO,
197
412
  " return 0;",
198
413
  "}",
199
414
  "",
200
415
  `int ${name}_send_command(${type} *dev, uint16_t command) {`,
201
- " uint8_t msb;",
202
- " uint8_t lsb;",
203
- " if (dev == NULL) {",
204
- " return -1;",
205
- " }",
206
- " msb = (uint8_t)(command >> 8);",
207
- " lsb = (uint8_t)(command & 0xFF);",
208
- " /* Command frame = [MSB][LSB]. hal_i2c_write emits the reg byte then the",
209
- " * data bytes, so MSB-as-reg + LSB-as-data yields the correct 2-byte frame. */",
210
- " hal_i2c_write(dev->i2c_addr, msb, &lsb, 1);",
211
- " return 0;",
416
+ ...(uart
417
+ ? uartFramingBody(["dev", "command"])
418
+ : can
419
+ ? canFramingBody(["dev", "command"])
420
+ : [
421
+ " uint8_t msb;",
422
+ " uint8_t lsb;",
423
+ " if (dev == NULL) {",
424
+ " return -1;",
425
+ " }",
426
+ " msb = (uint8_t)(command >> 8);",
427
+ " lsb = (uint8_t)(command & 0xFF);",
428
+ ...I2C_COMMAND_FRAME_NOTE,
429
+ " hal_i2c_write(dev->i2c_addr, msb, &lsb, 1);",
430
+ " return 0;",
431
+ ]),
212
432
  "}",
213
433
  "",
214
434
  `int ${name}_read_data(${type} *dev, uint8_t *buffer, uint16_t len) {`,
215
- " if (dev == NULL || buffer == NULL) {",
216
- " return -1;",
217
- " }",
218
- " /* TODO(driverge): read `len` response bytes (data words each followed by a",
219
- " * CRC byte). Most command-set parts use a plain I2C read with no pointer",
220
- " * byte adapt the HAL call to your platform, then check each word with",
221
- ` * ${name}_crc8(). See fill_in_brief.quirks_todo. */`,
222
- " (void)dev;",
223
- " (void)buffer;",
224
- " (void)len;",
225
- " return 0;",
435
+ ...(uart
436
+ ? uartFramingBody(["dev", "buffer", "len"])
437
+ : can
438
+ ? canFramingBody(["dev", "buffer", "len"])
439
+ : [
440
+ " if (dev == NULL || buffer == NULL) {",
441
+ " return -1;",
442
+ " }",
443
+ ...commandReadDataTodo(`${name}_crc8`),
444
+ " (void)dev;",
445
+ " (void)buffer;",
446
+ " (void)len;",
447
+ " return 0;",
448
+ ]),
226
449
  "}",
227
450
  "",
228
451
  ];
229
452
  if (crc) {
230
- source.push(`uint8_t ${name}_crc8(const uint8_t *data, uint16_t len) {`, " (void)data;", " (void)len;", ` /* TODO(driverge): implement CRC-${crc.width} (poly ${prefix}_CRC_POLY, init`, ` * ${prefix}_CRC_INIT) per the datasheet checksum section. See`, " * fill_in_brief.crc_todo. */", " return 0;", "}", "");
453
+ source.push(...crc8Body(`${name}_crc8`, crc, prefix));
231
454
  }
455
+ const brief = commandBrief(json, commands, crc, uart, can, name, prefix);
456
+ return { files: makeFiles(name, header, source), fill_in_brief: brief };
457
+ }
458
+ export function commandBrief(json, commands, crc, uart, can, name, prefix, calls) {
459
+ const c = {
460
+ sendCommand: calls?.sendCommand ?? `${name}_send_command`,
461
+ readData: calls?.readData ?? `${name}_read_data`,
462
+ crc8: calls?.crc8 ?? `${name}_crc8`,
463
+ };
232
464
  const brief = {
233
465
  init_sequence_todo: `Determine the power-up / soft-reset sequence and any startup delay for ${json.metadata.part || "the device"}.`,
234
- quirks_todo: `Implement ${name}_read_data for this part's actual read framing (clock stretching vs. fixed delay, number of data+CRC words per command) using the driverge://datasheet resource.`,
466
+ quirks_todo: uart
467
+ ? `Verify the response checksum algorithm from the datasheet before trusting ${c.readData}'s output — vendor checksum/CRC methods vary by part.`
468
+ : can
469
+ ? CAN_QUIRKS_NOTE
470
+ : `Implement ${c.readData} for this part's actual read framing (clock stretching vs. fixed delay, number of data+CRC words per command) using the driverge://datasheet resource.`,
235
471
  doc_todo: "Add Doxygen comments for all public functions and document each command's response layout.",
236
472
  };
237
473
  if (crc) {
238
- brief.crc_todo = `Implement ${name}_crc8: CRC-${crc.width}, polynomial ${crc.poly}, init ${crc.init}, per the datasheet checksum section.`;
474
+ brief.crc_todo = `Implement ${c.crc8}: CRC-${crc.width}, polynomial ${crc.poly}, init ${crc.init}, per the datasheet checksum section.`;
239
475
  }
240
- return { files: makeFiles(name, header, source), fill_in_brief: brief };
476
+ if (commands.length === 0) {
477
+ brief.command_set_todo = commandSetTodoText(json, name, prefix);
478
+ }
479
+ if (uart) {
480
+ brief.framing_todo = `Implement ${name}'s UART frame protocol in ${c.sendCommand}/${c.readData}: build request frames with hal_uart_write and parse responses with hal_uart_read, per the datasheet's communication/frame-format section.`;
481
+ }
482
+ if (can) {
483
+ brief.framing_todo = `Implement ${name}'s CAN frame protocol in ${c.sendCommand}/${c.readData}: build request frames and call hal_can_transfer() with this part's arbitration id(s), per the datasheet's CAN communication/message-ID section.`;
484
+ }
485
+ return brief;
241
486
  }
242
487
  // ---------------------------------------------------------------------------
243
- function makeFiles(name, header, source) {
488
+ /**
489
+ * Static prose block shared by the C and cpp command_set init() bodies
490
+ * (mentions no function names, so there is nothing to parameterize).
491
+ */
492
+ export const COMMAND_INIT_TODO = [
493
+ " /* TODO(driverge): power-up / soft-reset sequence and startup delay. See",
494
+ " * fill_in_brief.init_sequence_todo. */",
495
+ ];
496
+ /** The I2C command-frame comment in send_command — no callable names, shared verbatim. */
497
+ export const I2C_COMMAND_FRAME_NOTE = [
498
+ " /* Command frame = [MSB][LSB]. hal_i2c_write emits the reg byte then the",
499
+ " * data bytes, so MSB-as-reg + LSB-as-data yields the correct 2-byte frame. */",
500
+ ];
501
+ /**
502
+ * read_data's "implement the actual read framing" TODO comment. `crc8Name` is
503
+ * the bare callable name (no parens) — `<name>_crc8` for C, `crc8` for cpp.
504
+ */
505
+ export function commandReadDataTodo(crc8Name) {
506
+ return [
507
+ " /* TODO(driverge): read `len` response bytes (data words each followed by a",
508
+ " * CRC byte). Most command-set parts use a plain I2C read with no pointer",
509
+ " * byte — adapt the HAL call to your platform, then check each word with",
510
+ ` * ${crc8Name}(). See fill_in_brief.quirks_todo. */`,
511
+ ];
512
+ }
513
+ /**
514
+ * Renders a CRC-8 stub function/method body. `qualifiedName` is the full
515
+ * callable name to emit — a free function (`sht3x_crc8`) for C or a qualified
516
+ * method (`Sht3x::crc8`) for cpp — so this single builder covers both without
517
+ * duplicating the TODO prose (Session D: cpp language option).
518
+ */
519
+ export function crc8Body(qualifiedName, crc, prefix) {
520
+ return [
521
+ `uint8_t ${qualifiedName}(const uint8_t *data, uint16_t len) {`,
522
+ " (void)data;",
523
+ " (void)len;",
524
+ ` /* TODO(driverge): implement CRC-${crc.width} (poly ${prefix}_CRC_POLY, init`,
525
+ ` * ${prefix}_CRC_INIT) per the datasheet checksum section. See`,
526
+ " * fill_in_brief.crc_todo. */",
527
+ " return 0;",
528
+ "}",
529
+ "",
530
+ ];
531
+ }
532
+ /**
533
+ * register_map init()'s "implement the power-on/reset sequence" TODO comment.
534
+ * The uart/can branch mentions no callable names (just the seam functions), so
535
+ * it is identical for C and cpp; the register-access branch takes the write
536
+ * call as a parameter (`<name>_write_register()` for C, `writeRegister()` for
537
+ * cpp) so both flavors share this one builder (Session D: cpp language
538
+ * option).
539
+ */
540
+ export function registerInitTodo(uart, can, writeRegisterCall) {
541
+ if (uart || can) {
542
+ return [
543
+ " /* TODO(driverge): implement the power-on / reset init sequence — the",
544
+ " * correct command/frame sequence and any required startup delay. See",
545
+ " * fill_in_brief.init_sequence_todo and fill_in_brief.framing_todo. Use",
546
+ ` * ${uart ? "hal_uart_write()/hal_uart_read()" : "hal_can_transfer()"} and hal_delay_ms(). */`,
547
+ ];
548
+ }
549
+ return [
550
+ " /* TODO(driverge): implement the power-on / reset init sequence — the",
551
+ " * correct register write order and values, plus any required startup",
552
+ ` * delay. See fill_in_brief.init_sequence_todo. Use ${writeRegisterCall}`,
553
+ " * and hal_delay_ms(). */",
554
+ ];
555
+ }
556
+ export function makeFiles(name, header, source, ext = { header: "h", source: "c" }) {
244
557
  return [
245
- { path: `${name}.h`, content: `${header.join("\n").replace(/\n{3,}/g, "\n\n")}` },
246
- { path: `${name}.c`, content: `${source.join("\n").replace(/\n{3,}/g, "\n\n")}` },
558
+ { path: `${name}.${ext.header}`, content: `${header.join("\n").replace(/\n{3,}/g, "\n\n")}` },
559
+ { path: `${name}.${ext.source}`, content: `${source.join("\n").replace(/\n{3,}/g, "\n\n")}` },
247
560
  ];
248
561
  }
249
- /** Render the portable thin-HAL driver skeleton for a validated datasheet. */
562
+ /**
563
+ * Render the portable thin-HAL driver skeleton for a validated datasheet.
564
+ * DeviceInterface is a 2-member discriminated union, so narrowing on `kind`
565
+ * here — rather than casting inside registerDriver/commandDriver — lets both
566
+ * take an explicit, correctly-typed array with no cast. Any kind other than
567
+ * "command_set" is treated as register_map, matching buildInterface's
568
+ * register-first fallback (see schema/assemble) exactly.
569
+ */
250
570
  export function generatePortableDriver(json) {
251
571
  const part = json.metadata.part;
252
572
  const name = slug(part);
253
573
  const prefix = macro(name);
254
574
  return json.interface.kind === "command_set"
255
- ? commandDriver(json, name, prefix)
256
- : registerDriver(json, name, prefix);
575
+ ? commandDriver(json, json.interface.commands, name, prefix)
576
+ : registerDriver(json, json.interface.registers, name, prefix);
257
577
  }
258
578
  //# sourceMappingURL=portable.js.map