@typecad/framework-arduino 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/arduino/i2c-arduino.d.ts +47 -0
- package/dist/arduino/i2c-arduino.js +6 -0
- package/dist/arduino/index.d.ts +6 -0
- package/dist/arduino/index.js +8 -0
- package/dist/arduino/spi-arduino.d.ts +42 -0
- package/dist/arduino/spi-arduino.js +6 -0
- package/dist/arduino/stubs-uno.d.ts +9 -0
- package/dist/arduino/stubs-uno.js +65 -0
- package/dist/arduino/uart-arduino.d.ts +41 -0
- package/dist/arduino/uart-arduino.js +6 -0
- package/dist/arduino-class-map.d.ts +5 -0
- package/dist/arduino-class-map.js +21 -0
- package/dist/arduino-compile.d.ts +24 -0
- package/dist/arduino-compile.js +316 -0
- package/dist/arduino-libs.d.ts +6 -0
- package/dist/arduino-libs.js +11 -0
- package/dist/cli-metadata.d.ts +12 -0
- package/dist/cli-metadata.js +196 -0
- package/dist/cpp-parser.d.ts +47 -0
- package/dist/cpp-parser.js +292 -0
- package/dist/debug-codegen.d.ts +20 -0
- package/dist/debug-codegen.js +115 -0
- package/dist/displays/ili9341-spi.d.ts +6 -0
- package/dist/displays/ili9341-spi.js +28 -0
- package/dist/displays/ssd1309-i2c.d.ts +2 -0
- package/dist/displays/ssd1309-i2c.js +16 -0
- package/dist/displays/st7796-spi.d.ts +2 -0
- package/dist/displays/st7796-spi.js +20 -0
- package/dist/graphics/ili9341.d.ts +24 -0
- package/dist/graphics/ili9341.js +59 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +27 -0
- package/dist/lib-declaration.d.ts +35 -0
- package/dist/lib-declaration.js +399 -0
- package/dist/lib-discovery.d.ts +38 -0
- package/dist/lib-discovery.js +189 -0
- package/dist/profile.d.ts +8 -0
- package/dist/profile.js +584 -0
- package/dist/strategy.d.ts +174 -0
- package/dist/strategy.js +2032 -0
- package/package.json +73 -0
- package/src/arduino/i2c-arduino.ts +72 -0
- package/src/arduino/index.ts +18 -0
- package/src/arduino/spi-arduino.ts +63 -0
- package/src/arduino/stubs-uno.ts +80 -0
- package/src/arduino/uart-arduino.ts +59 -0
- package/src/arduino-class-map.ts +32 -0
- package/src/arduino-compile.ts +359 -0
- package/src/arduino-libs.ts +33 -0
- package/src/cli-metadata.ts +250 -0
- package/src/cpp-parser.ts +361 -0
- package/src/debug-codegen.ts +155 -0
- package/src/displays/ili9341-spi.ts +33 -0
- package/src/displays/ssd1309-i2c.ts +19 -0
- package/src/displays/st7796-spi.ts +23 -0
- package/src/graphics/ili9341.ts +80 -0
- package/src/index.ts +37 -0
- package/src/lib-declaration.ts +493 -0
- package/src/lib-discovery.ts +267 -0
- package/src/profile.ts +676 -0
- package/src/strategy.ts +2126 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/framework-arduino — C++ Header Parser
|
|
3
|
+
//
|
|
4
|
+
// Parses C++ header files from Arduino libraries into an IR that can be
|
|
5
|
+
// used for TypeScript type declaration generation.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Arduino-to-TypeCAD type mappings
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Maps Arduino peripheral C++ types to TypeCAD equivalents.
|
|
14
|
+
* Uses the instance names (I2C0, SPI0, UART0) as types for simplicity.
|
|
15
|
+
*/
|
|
16
|
+
const ARDUINO_TYPE_MAPPINGS: Record<string, string> = {
|
|
17
|
+
// I2C - TypeCAD uses I2C0 instance
|
|
18
|
+
"TwoWire": "I2C0",
|
|
19
|
+
"TwoWire*": "I2C0",
|
|
20
|
+
"TwoWire&": "I2C0",
|
|
21
|
+
// SPI - TypeCAD uses SPI0 instance
|
|
22
|
+
"SPIClass": "SPI0",
|
|
23
|
+
"SPIClass*": "SPI0",
|
|
24
|
+
"SPIClass&": "SPI0",
|
|
25
|
+
// Serial - TypeCAD uses UART0 instance
|
|
26
|
+
"HardwareSerial": "UART0",
|
|
27
|
+
"HardwareSerial*": "UART0",
|
|
28
|
+
"HardwareSerial&": "UART0",
|
|
29
|
+
"Stream": "UART0",
|
|
30
|
+
"Stream*": "UART0",
|
|
31
|
+
"Stream&": "UART0",
|
|
32
|
+
// Common Arduino types
|
|
33
|
+
"Print": "UART0",
|
|
34
|
+
"Print*": "UART0",
|
|
35
|
+
"Print&": "UART0",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Parse result types
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
interface CppMethod {
|
|
43
|
+
name: string;
|
|
44
|
+
returnType: string;
|
|
45
|
+
parameters: { type: string; name: string }[];
|
|
46
|
+
isPublic: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface CppClass {
|
|
50
|
+
name: string;
|
|
51
|
+
/** Fully qualified name including namespace (e.g., "Microfire::SHT3x") */
|
|
52
|
+
fullName: string;
|
|
53
|
+
/** Namespace prefix (e.g., "Microfire") */
|
|
54
|
+
namespace?: string;
|
|
55
|
+
methods: CppMethod[];
|
|
56
|
+
constructors: { parameters: { type: string; name: string }[] }[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface CppParseResult {
|
|
60
|
+
classes: CppClass[];
|
|
61
|
+
constants: { name: string; type: string; value: string }[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Type mapping
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Maps C++ types to TypeScript types.
|
|
70
|
+
*/
|
|
71
|
+
export function mapCppTypeToTs(cppType: string): string {
|
|
72
|
+
const trimmed = cppType.trim();
|
|
73
|
+
|
|
74
|
+
// Remove const qualifier
|
|
75
|
+
const withoutConst = trimmed.replace(/^const\s+/, "");
|
|
76
|
+
|
|
77
|
+
// Check Arduino type mappings first
|
|
78
|
+
if (ARDUINO_TYPE_MAPPINGS[withoutConst]) {
|
|
79
|
+
return ARDUINO_TYPE_MAPPINGS[withoutConst];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Basic type mappings
|
|
83
|
+
const typeMap: Record<string, string> = {
|
|
84
|
+
"int": "number",
|
|
85
|
+
"unsigned int": "number",
|
|
86
|
+
"uint8_t": "number",
|
|
87
|
+
"uint16_t": "number",
|
|
88
|
+
"uint32_t": "number",
|
|
89
|
+
"int8_t": "number",
|
|
90
|
+
"int16_t": "number",
|
|
91
|
+
"int32_t": "number",
|
|
92
|
+
"float": "number",
|
|
93
|
+
"double": "number",
|
|
94
|
+
"bool": "boolean",
|
|
95
|
+
"void": "void",
|
|
96
|
+
"char": "string",
|
|
97
|
+
"char*": "string",
|
|
98
|
+
"const char*": "string",
|
|
99
|
+
"std::string": "string",
|
|
100
|
+
"String": "string",
|
|
101
|
+
"byte": "number",
|
|
102
|
+
"word": "number",
|
|
103
|
+
"size_t": "number",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Check direct mapping
|
|
107
|
+
if (typeMap[withoutConst]) {
|
|
108
|
+
return typeMap[withoutConst];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle pointers - check if base type is an Arduino type
|
|
112
|
+
if (withoutConst.endsWith("*")) {
|
|
113
|
+
const baseType = withoutConst.slice(0, -1).trim();
|
|
114
|
+
if (ARDUINO_TYPE_MAPPINGS[baseType]) {
|
|
115
|
+
return ARDUINO_TYPE_MAPPINGS[baseType];
|
|
116
|
+
}
|
|
117
|
+
return "number";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Handle references - check if base type is an Arduino type
|
|
121
|
+
if (withoutConst.endsWith("&")) {
|
|
122
|
+
const baseType = withoutConst.slice(0, -1).trim();
|
|
123
|
+
if (ARDUINO_TYPE_MAPPINGS[baseType]) {
|
|
124
|
+
return ARDUINO_TYPE_MAPPINGS[baseType];
|
|
125
|
+
}
|
|
126
|
+
return mapCppTypeToTs(baseType);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Default to any for unknown types
|
|
130
|
+
return "any";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Parameter parsing
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Extracts the parameter list from a function signature.
|
|
139
|
+
*/
|
|
140
|
+
export function parseParameters(paramString: string): { type: string; name: string }[] {
|
|
141
|
+
if (!paramString.trim()) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const params: { type: string; name: string }[] = [];
|
|
146
|
+
const parts = paramString.split(",");
|
|
147
|
+
|
|
148
|
+
for (const part of parts) {
|
|
149
|
+
const trimmed = part.trim();
|
|
150
|
+
if (!trimmed) continue;
|
|
151
|
+
|
|
152
|
+
// Handle default values (remove them for the declaration)
|
|
153
|
+
const withoutDefault = trimmed.split("=")[0].trim();
|
|
154
|
+
|
|
155
|
+
// Split into tokens to find type and name
|
|
156
|
+
const tokens = withoutDefault.split(/\s+/);
|
|
157
|
+
if (tokens.length >= 2) {
|
|
158
|
+
// Last token is the name, rest is the type
|
|
159
|
+
let name = tokens[tokens.length - 1];
|
|
160
|
+
let type = tokens.slice(0, -1).join(" ");
|
|
161
|
+
|
|
162
|
+
// Strip & and * prefixes from parameter names (C++ reference/pointer syntax)
|
|
163
|
+
// These can appear as &name or *name when the type doesn't include them
|
|
164
|
+
if (name.startsWith("&") || name.startsWith("*")) {
|
|
165
|
+
// Move the & or * to the type
|
|
166
|
+
type = type + name[0];
|
|
167
|
+
name = name.slice(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
params.push({ type: mapCppTypeToTs(type), name });
|
|
171
|
+
} else if (tokens.length === 1) {
|
|
172
|
+
// Just a type (unnamed parameter)
|
|
173
|
+
params.push({ type: mapCppTypeToTs(tokens[0]), name: "" });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return params;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Class parsing helpers
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Extract content inside a namespace block.
|
|
186
|
+
*/
|
|
187
|
+
function extractNamespaceContent(content: string, namespaceName: string): string {
|
|
188
|
+
// Find namespace block: namespace Name { ... }
|
|
189
|
+
const namespaceRegex = new RegExp(`namespace\\s+${namespaceName}\\s*\\{`, 'g');
|
|
190
|
+
const match = namespaceRegex.exec(content);
|
|
191
|
+
if (!match) return "";
|
|
192
|
+
|
|
193
|
+
const startIndex = match.index + match[0].length;
|
|
194
|
+
let braceCount = 1;
|
|
195
|
+
let endIndex = startIndex;
|
|
196
|
+
|
|
197
|
+
while (endIndex < content.length && braceCount > 0) {
|
|
198
|
+
if (content[endIndex] === '{') braceCount++;
|
|
199
|
+
else if (content[endIndex] === '}') braceCount--;
|
|
200
|
+
endIndex++;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return content.slice(startIndex, endIndex - 1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Parse classes from content, optionally within a namespace.
|
|
208
|
+
*/
|
|
209
|
+
function parseClassesFromContent(content: string, namespace?: string): CppClass[] {
|
|
210
|
+
const classes: CppClass[] = [];
|
|
211
|
+
|
|
212
|
+
// Parse class definitions - handle nested braces properly
|
|
213
|
+
const classStartRegex = /\bclass\s+(\w+)\s*\{?/g;
|
|
214
|
+
let classStartMatch;
|
|
215
|
+
|
|
216
|
+
while ((classStartMatch = classStartRegex.exec(content)) !== null) {
|
|
217
|
+
const className = classStartMatch[1];
|
|
218
|
+
const fullName = namespace ? `${namespace}::${className}` : className;
|
|
219
|
+
|
|
220
|
+
const cppClass: CppClass = {
|
|
221
|
+
name: className,
|
|
222
|
+
fullName,
|
|
223
|
+
namespace,
|
|
224
|
+
methods: [],
|
|
225
|
+
constructors: [],
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// For header files, try to find method declarations
|
|
229
|
+
// Match: type name(params);
|
|
230
|
+
const methodRegex = /(\w+(?:\s*[*&])?)\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?;/g;
|
|
231
|
+
let methodMatch;
|
|
232
|
+
|
|
233
|
+
while ((methodMatch = methodRegex.exec(content)) !== null) {
|
|
234
|
+
const returnType = methodMatch[1].trim();
|
|
235
|
+
const name = methodMatch[2].trim();
|
|
236
|
+
const params = methodMatch[3];
|
|
237
|
+
|
|
238
|
+
// Skip keywords
|
|
239
|
+
if (["public", "private", "protected", "virtual", "static", "class", "struct"].includes(returnType)) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check if this is a constructor (name matches class name)
|
|
244
|
+
if (name === className) {
|
|
245
|
+
cppClass.constructors.push({
|
|
246
|
+
parameters: parseParameters(params),
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
cppClass.methods.push({
|
|
250
|
+
returnType: mapCppTypeToTs(returnType),
|
|
251
|
+
name,
|
|
252
|
+
parameters: parseParameters(params),
|
|
253
|
+
isPublic: true,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
classes.push(cppClass);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return classes;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Main parser
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Parses a C++ class definition from header content.
|
|
270
|
+
*/
|
|
271
|
+
export function parseCppClass(content: string): CppParseResult {
|
|
272
|
+
const result: CppParseResult = {
|
|
273
|
+
classes: [],
|
|
274
|
+
constants: [],
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Parse top-level constants: const int NAME = value;
|
|
278
|
+
const constRegex = /const\s+(\w+)\s+(\w+)\s*=\s*([^;]+);/g;
|
|
279
|
+
let constMatch;
|
|
280
|
+
while ((constMatch = constRegex.exec(content)) !== null) {
|
|
281
|
+
result.constants.push({
|
|
282
|
+
type: mapCppTypeToTs(constMatch[1]),
|
|
283
|
+
name: constMatch[2],
|
|
284
|
+
value: constMatch[3].trim(),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Parse methods with scope resolution (ClassName::methodName) - for .cpp implementation files
|
|
289
|
+
const scopeResolutionRegex = /(?:^|\n)\s*(?:(\w+(?:\s*[*&])?)\s+)?(\w+)::(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?(?:\{|;)/g;
|
|
290
|
+
let scopeMatch;
|
|
291
|
+
const classesFromImpl = new Map<string, CppClass>();
|
|
292
|
+
|
|
293
|
+
while ((scopeMatch = scopeResolutionRegex.exec(content)) !== null) {
|
|
294
|
+
const returnType = scopeMatch[1]?.trim();
|
|
295
|
+
const className = scopeMatch[2];
|
|
296
|
+
const methodName = scopeMatch[3];
|
|
297
|
+
const params = scopeMatch[4];
|
|
298
|
+
|
|
299
|
+
// Skip if this looks like a namespace (e.g., std::something)
|
|
300
|
+
if (!returnType && className.toLowerCase() === className) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!classesFromImpl.has(className)) {
|
|
305
|
+
classesFromImpl.set(className, {
|
|
306
|
+
name: className,
|
|
307
|
+
fullName: className,
|
|
308
|
+
methods: [],
|
|
309
|
+
constructors: [],
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const cppClass = classesFromImpl.get(className)!;
|
|
314
|
+
|
|
315
|
+
// Check if this is a constructor (method name matches class name)
|
|
316
|
+
if (methodName === className) {
|
|
317
|
+
cppClass.constructors.push({
|
|
318
|
+
parameters: parseParameters(params),
|
|
319
|
+
});
|
|
320
|
+
} else if (returnType) {
|
|
321
|
+
cppClass.methods.push({
|
|
322
|
+
returnType: mapCppTypeToTs(returnType),
|
|
323
|
+
name: methodName,
|
|
324
|
+
parameters: parseParameters(params),
|
|
325
|
+
isPublic: true,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Add inferred classes to result
|
|
331
|
+
for (const cppClass of classesFromImpl.values()) {
|
|
332
|
+
result.classes.push(cppClass);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Parse namespaces first
|
|
336
|
+
const namespaceRegex = /namespace\s+(\w+)\s*\{/g;
|
|
337
|
+
let nsMatch;
|
|
338
|
+
const namespaces: string[] = [];
|
|
339
|
+
|
|
340
|
+
while ((nsMatch = namespaceRegex.exec(content)) !== null) {
|
|
341
|
+
namespaces.push(nsMatch[1]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Parse classes within each namespace
|
|
345
|
+
for (const ns of namespaces) {
|
|
346
|
+
const nsContent = extractNamespaceContent(content, ns);
|
|
347
|
+
const nsClasses = parseClassesFromContent(nsContent, ns);
|
|
348
|
+
result.classes.push(...nsClasses);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Parse classes outside of namespaces (skip if already found in a namespace)
|
|
352
|
+
const namespaceClassNames = new Set(result.classes.map(c => c.name));
|
|
353
|
+
const topLevelClasses = parseClassesFromContent(content);
|
|
354
|
+
for (const cls of topLevelClasses) {
|
|
355
|
+
if (!namespaceClassNames.has(cls.name)) {
|
|
356
|
+
result.classes.push(cls);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Arduino debug code generator — Serial-based debug output
|
|
3
|
+
//
|
|
4
|
+
// Generates Arduino Serial.print/println code for debug breakpoints,
|
|
5
|
+
// logpoints, and initialization. Used by the CLI debug preprocessor.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export interface CapturedVariable {
|
|
9
|
+
name: string;
|
|
10
|
+
isFunction?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface LogMessagePart {
|
|
14
|
+
type: 'text' | 'variable';
|
|
15
|
+
value: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate Serial.begin() initialization code for Arduino debug mode.
|
|
20
|
+
*/
|
|
21
|
+
export function generateSerialInitCode(): string[] {
|
|
22
|
+
return [
|
|
23
|
+
`// === DEBUG: Initialize Serial ===`,
|
|
24
|
+
`Serial.begin(9600);`,
|
|
25
|
+
`while (!Serial) {`,
|
|
26
|
+
` delay(10);`,
|
|
27
|
+
`}`,
|
|
28
|
+
`Serial.println("🔧 TypeCAD Debug Mode Active");`,
|
|
29
|
+
`Serial.println("");`,
|
|
30
|
+
`// === END DEBUG INIT ===`,
|
|
31
|
+
``,
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate the Serial debug code for a breakpoint.
|
|
37
|
+
*/
|
|
38
|
+
export function generateBreakpointCode(
|
|
39
|
+
fileName: string,
|
|
40
|
+
lineNum: number,
|
|
41
|
+
originalLine: string,
|
|
42
|
+
variables: CapturedVariable[],
|
|
43
|
+
normalizedCondition: string | undefined,
|
|
44
|
+
): string[] {
|
|
45
|
+
const lines: string[] = [];
|
|
46
|
+
|
|
47
|
+
// Comment marker
|
|
48
|
+
lines.push(` // === BREAKPOINT: ${fileName}:${lineNum} ===`);
|
|
49
|
+
|
|
50
|
+
// If conditional, wrap everything in an if statement
|
|
51
|
+
if (normalizedCondition) {
|
|
52
|
+
lines.push(` if (${normalizedCondition}) {`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Visual separator
|
|
56
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");`);
|
|
57
|
+
|
|
58
|
+
// Breakpoint header (with condition indicator if present)
|
|
59
|
+
const headerText = normalizedCondition
|
|
60
|
+
? `⏸️ BREAKPOINT: ${fileName}:${lineNum} (condition: ${escapeString(normalizedCondition)})`
|
|
61
|
+
: `⏸️ BREAKPOINT: ${fileName}:${lineNum}`;
|
|
62
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println("${headerText}");`);
|
|
63
|
+
|
|
64
|
+
// Original source line (escaped)
|
|
65
|
+
const escapedLine = escapeString(originalLine);
|
|
66
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println(" ${escapedLine}");`);
|
|
67
|
+
|
|
68
|
+
// Blank line
|
|
69
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println("");`);
|
|
70
|
+
|
|
71
|
+
// Variables section
|
|
72
|
+
if (variables.length > 0) {
|
|
73
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println(" Variables:");`);
|
|
74
|
+
for (const v of variables) {
|
|
75
|
+
if (v.isFunction) {
|
|
76
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println(" • ${v.name} = [function]");`);
|
|
77
|
+
} else {
|
|
78
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.print(" • ${v.name} = "); Serial.println(${v.name});`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println(" (no variables in scope)");`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Blank line
|
|
86
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println("");`);
|
|
87
|
+
|
|
88
|
+
// Continue prompt
|
|
89
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println(" [Press ENTER to continue...]");`);
|
|
90
|
+
|
|
91
|
+
// Visual separator (end)
|
|
92
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");`);
|
|
93
|
+
|
|
94
|
+
// Blocking wait for serial input
|
|
95
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}while(Serial.available() == 0) { delay(10); }`);
|
|
96
|
+
lines.push(` ${normalizedCondition ? ' ' : ''}while(Serial.available() > 0) { Serial.read(); delay(10); }`);
|
|
97
|
+
|
|
98
|
+
// Close conditional if statement
|
|
99
|
+
if (normalizedCondition) {
|
|
100
|
+
lines.push(` }`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// End marker
|
|
104
|
+
lines.push(` // === END BREAKPOINT ===`);
|
|
105
|
+
|
|
106
|
+
return lines;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generate code for a logpoint (logs message without stopping execution).
|
|
111
|
+
*/
|
|
112
|
+
export function generateLogpointCode(
|
|
113
|
+
fileName: string,
|
|
114
|
+
lineNum: number,
|
|
115
|
+
parts: LogMessagePart[],
|
|
116
|
+
variables: CapturedVariable[],
|
|
117
|
+
): string[] {
|
|
118
|
+
const lines: string[] = [];
|
|
119
|
+
|
|
120
|
+
// Comment marker
|
|
121
|
+
lines.push(` // === LOGPOINT: ${fileName}:${lineNum} ===`);
|
|
122
|
+
|
|
123
|
+
// Build the Serial output
|
|
124
|
+
lines.push(` Serial.print("[LOG ${fileName}:${lineNum}] ");`);
|
|
125
|
+
|
|
126
|
+
for (const part of parts) {
|
|
127
|
+
if (part.type === 'text') {
|
|
128
|
+
lines.push(` Serial.print("${escapeString(part.value)}");`);
|
|
129
|
+
} else if (part.type === 'variable') {
|
|
130
|
+
// Check if the variable exists in scope
|
|
131
|
+
const varExists = variables.some(v => v.name === part.value && !v.isFunction);
|
|
132
|
+
if (varExists) {
|
|
133
|
+
lines.push(` Serial.print(${part.value});`);
|
|
134
|
+
} else {
|
|
135
|
+
lines.push(` Serial.print("{${part.value}}"); // variable not in scope`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
lines.push(` Serial.println("");`);
|
|
141
|
+
lines.push(` // === END LOGPOINT ===`);
|
|
142
|
+
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Escape a string for use in Serial.println("...").
|
|
148
|
+
*/
|
|
149
|
+
function escapeString(s: string): string {
|
|
150
|
+
return s
|
|
151
|
+
.replace(/\\/g, '\\\\')
|
|
152
|
+
.replace(/"/g, '\\"')
|
|
153
|
+
.replace(/\n/g, '\\n')
|
|
154
|
+
.replace(/\r/g, '\\r');
|
|
155
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Built-in display profiles for @typecad/framework-arduino.
|
|
3
|
+
//
|
|
4
|
+
// Each profile describes a common display's capabilities. Projects reference
|
|
5
|
+
// them by name in cuttlefish.config.ts:
|
|
6
|
+
// display: { profile: 'ili9341-spi', cs: 5, dc: 21, rst: 22 }
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import type { DisplayProfile } from "@typecad/cuttlefish/api/shared";
|
|
10
|
+
import { SSD1309_I2C } from "./ssd1309-i2c.js";
|
|
11
|
+
import { ST7796_SPI } from "./st7796-spi.js";
|
|
12
|
+
|
|
13
|
+
export const ILI9341_SPI: DisplayProfile = {
|
|
14
|
+
driver: "ili9341",
|
|
15
|
+
width: 320,
|
|
16
|
+
height: 240,
|
|
17
|
+
colorFormat: "rgb565",
|
|
18
|
+
rotation: 1,
|
|
19
|
+
backlight: 17,
|
|
20
|
+
spiPins: { mosi: 23, sck: 18, miso: 19 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Registry of all built-in profiles. Keyed by profile name. */
|
|
24
|
+
export const BUILT_IN_PROFILES: Record<string, DisplayProfile> = {
|
|
25
|
+
"ili9341-spi": ILI9341_SPI,
|
|
26
|
+
"st7796-spi": ST7796_SPI,
|
|
27
|
+
"ssd1309-i2c": SSD1309_I2C,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Get the registry as a Map (for resolveDisplayProfile). */
|
|
31
|
+
export function getProfileRegistry(): Map<string, DisplayProfile> {
|
|
32
|
+
return new Map(Object.entries(BUILT_IN_PROFILES));
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// SSD1309 OLED display profile — 128×64 1-bit monochrome OLED over I2C.
|
|
3
|
+
//
|
|
4
|
+
// The Adafruit_SSD1306 library drives SSD1309-class panels via the same API.
|
|
5
|
+
// displayClass: "oled" uses the same deferred, backing-store mono capability
|
|
6
|
+
// path as e-ink panels (UI_NATIVE_MONO + UI_REQUIRES_BACKING_STORE), while
|
|
7
|
+
// keeping the profile semantically accurate for page-buffered OLED hardware.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import type { DisplayProfile } from "@typecad/cuttlefish/api/shared";
|
|
11
|
+
|
|
12
|
+
export const SSD1309_I2C: DisplayProfile = {
|
|
13
|
+
driver: "ssd1309",
|
|
14
|
+
width: 128,
|
|
15
|
+
height: 64,
|
|
16
|
+
colorFormat: "mono",
|
|
17
|
+
displayClass: "oled",
|
|
18
|
+
rotation: 0,
|
|
19
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Built-in profile for the ST7796S 320×480 SPI TFT.
|
|
3
|
+
//
|
|
4
|
+
// Targets the Adafruit_ST7735_and_ST7789_Library fork that exposes the
|
|
5
|
+
// Adafruit_ST7796S class. RGB565 (the library hardcodes 565 in its init
|
|
6
|
+
// sequence; see display-adapters/st7796.ts for the 666 constraint).
|
|
7
|
+
//
|
|
8
|
+
// Reference usage in cuttlefish.config.ts:
|
|
9
|
+
// display: { profile: 'st7796-spi', cs: 5, dc: 17, rst: 16 }
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
import type { DisplayProfile } from "@typecad/cuttlefish/api/shared";
|
|
13
|
+
|
|
14
|
+
export const ST7796_SPI: DisplayProfile = {
|
|
15
|
+
driver: "st7796",
|
|
16
|
+
width: 480,
|
|
17
|
+
height: 320,
|
|
18
|
+
nativeWidth: 320,
|
|
19
|
+
nativeHeight: 480,
|
|
20
|
+
colorFormat: "rgb565",
|
|
21
|
+
rotation: 1, // landscape → effective 480×320
|
|
22
|
+
spiPins: { mosi: 23, sck: 18, miso: 19 },
|
|
23
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ILI9341 — display driver resolver built on the Adafruit_ILI9341 library.
|
|
3
|
+
//
|
|
4
|
+
// Maps each DisplayHALOp to a method call on a library-instantiated
|
|
5
|
+
// Adafruit_ILI9341 object. The library owns SPI, command sequences, fonts,
|
|
6
|
+
// and glyph rendering — we own the tree, layout, and reactive driver.
|
|
7
|
+
//
|
|
8
|
+
// The framework instantiates one library object per mount; ui.mount's
|
|
9
|
+
// display.init emits the constructor + begin() + setRotation(), and
|
|
10
|
+
// subsequent ops reference the same object by name (__tc_display).
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
import type { DisplayHALOp } from "@typecad/cuttlefish/api/shared";
|
|
14
|
+
|
|
15
|
+
/** Display-side object variable name (instantiated by display.init). */
|
|
16
|
+
export const DISPLAY_VAR = "__tc_display";
|
|
17
|
+
|
|
18
|
+
/** The library's required headers — emitted as forced includes. */
|
|
19
|
+
export const ILI9341_INCLUDES = ["<Adafruit_GFX.h>", "<Adafruit_ILI9341.h>"];
|
|
20
|
+
|
|
21
|
+
export interface ILI9341Context {
|
|
22
|
+
bus: string;
|
|
23
|
+
cs: number;
|
|
24
|
+
dc: number;
|
|
25
|
+
rst: number;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
rotation?: number;
|
|
29
|
+
backlight?: number;
|
|
30
|
+
spiFrequency?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Render a color value as a C++ hex literal (e.g. 0x07e0) for readable RGB565. */
|
|
34
|
+
function hexColor(c: number): string {
|
|
35
|
+
return `0x${c.toString(16)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a display HAL op to a method call on the Adafruit_ILI9341 object.
|
|
40
|
+
* Returns undefined for ops this driver does not handle.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveILI9341Op(
|
|
43
|
+
op: DisplayHALOp,
|
|
44
|
+
ctx: ILI9341Context,
|
|
45
|
+
): { code?: string; expression?: string } | undefined {
|
|
46
|
+
switch (op.operation) {
|
|
47
|
+
case "display.init":
|
|
48
|
+
return {
|
|
49
|
+
code: [
|
|
50
|
+
ctx.backlight ? `pinMode(${ctx.backlight}, OUTPUT); digitalWrite(${ctx.backlight}, HIGH);` : ``,
|
|
51
|
+
`display_init();`,
|
|
52
|
+
].filter(Boolean).join("\n"),
|
|
53
|
+
};
|
|
54
|
+
case "display.fill_rect":
|
|
55
|
+
// Adafruit_GFX fillRect(x, y, w, h, color) — color is uint16 RGB565.
|
|
56
|
+
return {
|
|
57
|
+
code: `${DISPLAY_VAR}.fillRect(${op.x}, ${op.y}, ${op.w}, ${op.h}, ${hexColor(op.color)});`,
|
|
58
|
+
};
|
|
59
|
+
case "display.draw_rect":
|
|
60
|
+
return {
|
|
61
|
+
code: `${DISPLAY_VAR}.drawRect(${op.x}, ${op.y}, ${op.w}, ${op.h}, ${hexColor(op.color)});`,
|
|
62
|
+
};
|
|
63
|
+
case "display.draw_text":
|
|
64
|
+
// Set cursor + color + size, then print. print() is the Print mixin
|
|
65
|
+
// (not in the auto-gen d.ts but present on the real C++ object).
|
|
66
|
+
return {
|
|
67
|
+
code: [
|
|
68
|
+
`${DISPLAY_VAR}.setCursor(${op.x}, ${op.y});`,
|
|
69
|
+
`${DISPLAY_VAR}.setTextColor(${hexColor(op.color)});`,
|
|
70
|
+
`${DISPLAY_VAR}.setTextSize(2);`,
|
|
71
|
+
`${DISPLAY_VAR}.print(${JSON.stringify(op.text)});`,
|
|
72
|
+
].join("\n"),
|
|
73
|
+
};
|
|
74
|
+
case "display.flush":
|
|
75
|
+
// ILI9341 is immediate — draws go straight to the panel. No flush.
|
|
76
|
+
return { code: `/* flush: ${op.rects.length} rect(s) — immediate draw */` };
|
|
77
|
+
default:
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|