Skip to main content

library-migration-guide

Complete guide for converting Arduino/ESP32 hardware libraries into Aily Blockly compatible format. Covers the full workflow: source analysis, block.json design, generator.js implementation, toolbox.json configuration, bus initialization (Serial/I2C/SPI), board adaptation, and packaging.

Quellinformationen

Repository
ailyProject/aily-blockly
Letzte Quellaktivität
9. Juni 2026 um 15:26
Erkannte Sprache von SKILL.md
Englisch
Sterne
3.830
Forks
221

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
library-migration-guide
description
Complete guide for converting Arduino/ESP32 hardware libraries into Aily Blockly compatible format. Covers the full workflow: source analysis, block.json design, generator.js implementation, toolbox.json configuration, bus initialization (Serial/I2C/SPI), board adaptation, and packaging.
metadata
{"version":"4.0.0","author":"aily-team","scope":"global","agents":"mainAgent","auto-activate":false,"tags":"library,migration,conversion,block-json,generator,serial,i2c,spi,board-config"}
# Blockly Library Conversion Guide A systematic guide for converting Arduino libraries into Aily Blockly libraries, based on real conversion cases (ArduinoJson, OneButton, MQTT/PubSubClient, DHT, INA219, VL53L0X, etc.). ## Conversion Workflow ### Prerequisites 1. Use the existing environment/project context first to determine whether a project is already open. 2. If no project exists, use `project` with `action="create"` to create one, then continue with the new project path. ### ⚠️ CRITICAL: Library Working Directory All library files **MUST** be created in `<projectPath>/<library-name>/`, NOT in `node_modules/`. - ✅ Correct: `<projectPath>/lib-grove_motor/block.json` - ❌ Wrong: `<projectPath>/node_modules/@aily-project/lib-grove_motor/block.json` The `node_modules/` directory is managed by npm and is write-protected. After creating the library locally, install it with: `npm install ./<library-name>` If a file write operation returns a path/permission error, **check your target path** — you are likely writing to `node_modules/` instead of `<projectPath>/<library-name>/`. Fix the path and retry. Do NOT attempt to bypass by using terminal commands (mkdir, echo, Out-File, etc.) to write into protected directories. ### Step-by-step Process 1. **Source Analysis**: Analyze the Arduino library header files to identify public APIs. Classify by operation type: initialization, connection, communication, status, maintenance, quick operations. 2. **Block Design**: Design user-friendly blocks following block type mapping rules (see Section "Block Design Rules" below). 3. **Create Library Files** in `<projectPath>/<library-name>/`: - `block.json` — Block definitions - `generator.js` — Code generator - `toolbox.json` — Toolbox configuration - `package.json` — Library metadata 4. **Copy Source Files**: Use `run_terminal` to copy the Arduino source: - If `src/` folder exists → copy to `<projectPath>/<library-name>/src/<library-name>/` - If no `src/` folder → copy `.c`, `.cpp`, `.h`, `.hpp` files to `<projectPath>/<library-name>/src/<library-name>/` 5. **Write README.md**: First read `Blockly_Library_README_Conventions.md` (fetch from `https://blockly.diandeng.tech/files/Blockly_Library_README_Conventions.md`), then follow its format. 6. **Post-conversion**: Ask the user if they need help with: - Installing: `npm i <library-path>` (must specify the local library path) - Testing the converted library - Opening the library folder location ### Library Directory Structure ``` library-name/ ├─ block.json // Block definitions ├─ generator.js // Code generator ├─ toolbox.json // Toolbox configuration ├─ package.json // Library metadata ├─ README.md // Human-readable documentation ├─ README_AI.md // LLM-readable documentation └─ src/ └─ library-name/ // Copied Arduino source files ``` --- ## Detailed Code Specification IMPORTANT: For complete code specification with detailed examples, read the companion file `Blockly_Library_CODE_Conventions.md` located in this skill's folder. It covers: - Full block.json design rules with templates for every block type - Complete generator.js implementation patterns with real-world examples - toolbox.json shadow blocks and organization - package.json configuration with board compatibility - Board adaptation patterns and WiFi library selection The sections below summarize the critical rules that MUST be followed. --- ## Block Design Rules ### Block Type Mapping | Arduino Pattern | Block Type | Connection | Field Type | |----------------|------------|------------|------------| | Object creation/init | Statement | prev/next | `field_input` (user enters new var name) | | Global object method | Statement | prev/next | No variable field (direct call) | | Object method call | Statement | prev/next | `field_variable` (select existing var) | | Global object query | Value | output | No variable field | | Quick operation | Statement/Value | standard | No variable field, direct params | | Event callback | Hat block | **No prev/next** | `field_variable` + `input_statement` | | Conditional callback | Hybrid | prev/next | `input_value` + `input_statement` | | Status query | Value | output | `field_variable` | ### field_input vs field_variable - **`field_input`**: For initialization blocks — user enters a NEW variable name - **`field_variable`**: For method call blocks — user selects an EXISTING variable (set `variableTypes` and `defaultType`) - **Global objects** (Serial, WiFi, Wire, SPI, httpUpdate, SPIFFS, ESP, EEPROM): No variable field needed ### Reading Variable Names in generator.js ```javascript // field_input const varName = block.getFieldValue('VAR') || 'defaultVar'; // field_variable const varField = block.getField('VAR'); const varName = varField ? varField.getText() : 'defaultVar'; // Global object — use directly const serialPort = block.getFieldValue('SERIAL') || 'Serial'; ``` ### Board Config Template Variables (block.json) Use these in `field_dropdown` options — auto-populated at runtime: | Variable | Usage | |----------|-------| | `${board.i2c}` | I2C interface list (Wire selector) | | `${board.digitalPins}` | Digital pin list | | `${board.analogPins}` | Analog pin list | | `${board.serialPort}` | Serial port list | | `${board.serialSpeed}` | Baud rate list | | `${board.interruptPins}` | Interrupt pin list | | `${board.interruptMode}` | Interrupt mode list | ### Extensions Register dynamic extensions in generator.js. Always unregister before registering: ```javascript if (Blockly.Extensions.isRegistered('ext_name')) { Blockly.Extensions.unregister('ext_name'); } Blockly.Extensions.register('ext_name', function() { /* ... */ }); ``` --- ## Code Generation Rules ### Injection Methods & Execution Order All injection methods take `(tag, code)` and auto-deduplicate by tag. ```cpp #include <Lib.h> // addLibrary(tag, code) #define MACRO val // addMacro(tag, code) Type globalVar; // addVariable(tag, code) MyClass obj; // addObject(tag, code) void helper() {} // addFunction(tag, code, isGlobal?) void setup() { Serial.begin(9600); // addSetupBegin — bus-level init ONLY sensor.begin(); // addSetup — device/sensor init attachCb(handler); // addSetupEnd — callbacks, depends on prior init } void loop() { btn.tick(); // addLoopBegin — polling/tick calls // [user blocks here] } ``` ### Bus Initialization (MANDATORY) **Serial** — Always use `ensureSerialBegin()`, NEVER write `Serial.begin()` directly: ```javascript // ✅ Correct ensureSerialBegin('Serial', generator); // default 9600 ensureSerialBegin('Serial', generator, 115200); // custom baud ensureSerialBegin(serialPort, generator, baud); // dynamic // ❌ FORBIDDEN generator.addSetupBegin('serial_begin', 'Serial.begin(9600);'); ``` **I2C** — Use `wire_${wireName}_begin` key for deduplication: ```javascript const wire = block.getFieldValue('WIRE') || 'Wire'; generator.addLibrary('Wire', '#include <Wire.h>'); const wireBeginKey = `wire_${wire}_begin`; if (!generator.setupCodes_ || !generator.setupCodes_[wireBeginKey]) { generator.addSetup(wireBeginKey, wire + '.begin();\n'); } ``` **SPI** — Use `spi_${spiName}_begin` key for deduplication: ```javascript const spi = block.getFieldValue('SPI') || 'SPI'; generator.addLibrary('SPI', '#include <SPI.h>'); generator.addSetup(`spi_${spi}_begin`, spi + '.begin();\n'); ``` ### Variable Management Initialization blocks with `field_input` MUST implement a rename listener: ```javascript if (!block._varMonitorAttached) { block._varMonitorAttached = true; block._varLastName = block.getFieldValue('VAR') || 'defaultVar'; registerVariableToBlockly(block._varLastName, 'VarType'); const varField = block.getField('VAR'); if (varField) { const orig = varField.onFinishEditing_; varField.onFinishEditing_ = function(newName) { if (typeof orig === 'function') orig.call(this, newName); const ws = block.workspace || Blockly.getMainWorkspace?.(); const oldName = block._varLastName; if (ws && newName && newName !== oldName) { renameVariableInBlockly(block, oldName, newName, 'VarType'); block._varLastName = newName; } }; } } ``` ### Generator Return Values - **Statement blocks**: `return 'code;\n';` - **Value blocks**: `return [expr, generator.ORDER_ATOMIC];` - **Hat / event blocks**: `return '';` (empty string — event-driven, not in main flow) - **Hybrid blocks**: `return 'conditional_code;\n';` (returned code runs inside parent callback) ### valueToCode & ORDER Constants ```javascript // Extract input value (most cases use ORDER_ATOMIC) const val = generator.valueToCode(block, 'INPUT', generator.ORDER_ATOMIC) || '0'; // Return value block return [varName + '.read()', generator.ORDER_FUNCTION_CALL]; ``` ### Board Adaptation Access runtime board config via `window['boardConfig']`: ```javascript const boardConfig = window['boardConfig']; // boardConfig.core — e.g. 'esp32:esp32', 'arduino:avr' // boardConfig.name — board display name // boardConfig.i2c — I2C interface list // boardConfig.digitalPins — digital pin list ``` NEVER modify `window['boardConfig']` directly — use independent storage like `window['customXxx']`. --- ## toolbox.json Rules **All `input_value` slots MUST have shadow blocks**: ```json { "kind": "block", "type": "sensor_read", "inputs": { "TIMEOUT": {"shadow": {"type": "math_number", "fields": {"NUM": 1000}}} } } ``` Organize blocks by user cognitive flow using `label` separators: ```json { "kind": "category", "name": "SensorLib", "contents": [ {"kind": "label", "text": "Setup"}, {"kind": "block", "type": "sensor_init"}, {"kind": "label", "text": "Read Data"}, {"kind": "block", "type": "sensor_read"} ] } ``` --- ## package.json Configuration ```json { "name": "@aily-project/lib-libname", "nickname": "Display Name", "description": "Brief description (<50 chars)", "version": "1.0.0", "compatibility": { "core": [], "voltage": [3.3, 5] }, "keywords": ["aily", "blockly"], "tested": true, "url": "original library URL" } ``` Board compatibility shorthand: - Universal: `"core": []` (empty array = all boards) - ESP32 only: `"core": ["esp32:esp32"]` - Classic Arduino: `"core": ["arduino:avr", "arduino:megaavr"]` - IoT boards: `"core": ["esp32:esp32", "esp8266:esp8266", "renesas_uno:unor4wifi"]` --- ## Common Anti-patterns | ❌ Wrong | ✅ Correct | |----------|-----------|
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen