Skip to content
cccskills
FreePublished on GitHub

library-migration-guide

Follow a workflow for converting Arduino and ESP32 libraries into Aily Blockly libraries. The guide covers analyzing public APIs, defining blocks, generating code, configuring toolboxes, initializing Serial, I2C, and SPI, adapting boards, and packaging the result.

OriginalShow the original 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.

ailyProject/aily-blockly3,859 stars(GitHub 3,859 + cccskills 0)

Run score Not tested

See how to install

Good for

  • Convert an Arduino library for Aily Blockly
  • Design block definitions and generated code
  • Adapt a library to supported development boards

Files included (2)

  • SKILL.md12.3 KB
  • Blockly_Library_CODE_Conventions.md27.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

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 PatternBlock TypeConnectionField Type
Object creation/initStatementprev/nextfield_input (user enters new var name)
Global object methodStatementprev/nextNo variable field (direct call)
Object method callStatementprev/nextfield_variable (select existing var)
Global object queryValueoutputNo variable field
Quick operationStatement/ValuestandardNo variable field, direct params
Event callbackHat blockNo prev/nextfield_variable + input_statement
Conditional callbackHybridprev/nextinput_value + input_statement
Status queryValueoutputfield_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

// 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:

VariableUsage
${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:

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.

#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:

// ✅ 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:

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:

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:

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

// 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']:

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:

{
  "kind": "block",
  "type": "sensor_read",
  "inputs": {
    "TIMEOUT": {"shadow": {"type": "math_number", "fields": {"NUM": 1000}}}
  }
}

Organize blocks by user cognitive flow using label separators:

{
  "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

{
  "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
Write Serial.begin() directlyUse ensureSerialBegin(port, generator)
Hardcoded Wire key 'WIRE_BEGIN'Use wire_${wireName}_begin formatted key
Modify window['boardConfig']Use window['customXxx'] for custom storage
addSetupBegin for sensor initaddSetupBegin is bus-only; use addSetup for sensors
No shadow blocks for input_valueAlways configure shadow blocks in toolbox.json
Extension register without unregisterAlways unregister() before register()
addFunction without 3rd paramPass true when helper function must be globally visible
Skip ensureSerialBegin in quick opsQuick operations using Serial.println need it too

Quality Checklist

  • Covers 80%+ core features of the original library
  • New users can get started within 10 minutes
  • Common tasks complete in ≤ 3 steps
  • Generated code compiles 100%
  • Supports target development boards
  • All input_value have shadow blocks in toolbox.json
  • Serial init uses ensureSerialBegin()
  • I2C init uses wire_${wire}_begin key deduplication
  • SPI init uses spi_${spi}_begin key deduplication
  • addSetupBegin only used for bus-level initialization
  • Extensions unregister before register
  • Never directly modifies window['boardConfig']
  • Variable rename listener implemented on field_input blocks

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use this reference to write Aily Block Syntax (ABS), a text format for Blockly scripts. It explains block connections, parameter order, statement inputs, values, and variable references, with examples for conditions, loops, and Arduino program structure.

  • Check the correct order of block parameters
  • Write conditions and loops in ABS
  • Look up value and variable syntax for Blockly scripts
ailyProject/aily-blockly3.9KUpdated Oct 26, 2026

Implement or modify Blockly/ABS programs using the project’s current board and installed libraries. Check only relevant library evidence, edit the canonical ABS source, sync it to the workspace, and run focused validation when needed.

  • Adding a feature to an existing Blockly program
  • Editing ABS source and syncing the workspace
  • Checking a focused build or syntax issue
ailyProject/aily-blockly3.9KUpdated Oct 26, 2026

Plan a new Blockly hardware project by discovering suitable board and library packages, then comparing viable combinations. Each plan outlines wiring assumptions, workspace structure, implementation steps, and validation before the user confirms project creation.

  • Comparing boards for a new Blockly project
  • Planning libraries for a sensor or actuator
  • Reviewing wiring and validation assumptions
ailyProject/aily-blockly3.9KUpdated Oct 26, 2026

chronicle

FreeSummary

Search indexed chat history to find earlier decisions, tool activity, project summaries, files, and workflow tips. It can also rebuild the local history index when requested or when search results appear out of date.

  • Find a decision made in an earlier conversation
  • Check which tools were used for past work
  • Summarize recent project sessions
ailyProject/aily-blockly3.9KUpdated Oct 26, 2026

handoff

FreeSummary

Create a concise handoff document in the operating system’s temporary directory so another agent can continue the work. It summarizes the conversation, suggests relevant skills, references existing artifacts, and redacts sensitive information.

  • Hand ongoing work to another agent
  • Prepare context for a fresh session
  • Summarize progress without duplicating project artifacts
ailyProject/aily-blockly3.9KUpdated Oct 26, 2026

improve

FreeSummary

Survey a codebase and turn verified improvement opportunities into prioritized, self-contained implementation plans for another agent. It can assess bugs, security, performance, tests, technical debt, migrations, tooling, and product direction while keeping source code read-only.

  • Audit a codebase for high impact issues
  • Plan fixes for security or performance findings
  • Create a grounded roadmap for another agent
ailyProject/aily-blockly3.9KUpdated Oct 26, 2026

See all skills by ailyProject

Report a problem with this skill