Completed items are marked [x]. Active and future phases are in order of planned work.
- Initial file structure and CMakeLists.txt
-
ArduinoAPIfunction pointer table — all Arduino calls go through injected struct -
ArduinoRuntime— implements allimpl_*functions, owns simulation state -
SketchThread— QThread runningvb_loop()on a background thread - First working Qt6 GUI with serial monitor output
- AutoCompile pipeline — file watch → g++ invocation → DLL hot-reload
- Output DLL placed in sketch subfolder (not build dir)
- Initial Preprocessor — transforms Arduino source to DLL format (
vb_init,vb_setup,vb_loop) - Circuit canvas (
CanvasWidget) with basic component rendering - Clickable Button component on canvas
-
CircuitDetectorkeyword scan — detects component types from#definenames - Delay consistency — simulated delay tracks sketch timing
Milestone: "Blink" sketch compiles, loads, and toggles the LED on the canvas. ✓
- Syntax highlighting — blue keywords, yellow functions, green comments, orange strings
- Compile error highlighting — red line backgrounds via
QTextEdit::ExtraSelection - Error line number correction — subtracts
INJECTED_HEADER_LINESso errors point to user sketch lines - Corrected error message names (temp file path stripped,
api->prefix stripped) - Line number gutter —
EditorWithLines+LineNumberAreasubclass - Auto-indent and auto-dedent — Enter carries indentation, Tab inserts 4 spaces,
}dedents - Variable watch panel —
QTableWidgetupdated fromwatch_variable()callbacks -
Serial.printlntype overloads — int, float, String, const char* -
Stringclass — wrapsstd::string, injected into preprocessor header - Math functions —
map(),constrain(),abs(),min(),max(),random() - Safety delay injection — preprocessor inserts
api->delay(10)if no delay found inloop(), prevents infinite loop crash
Milestone: Non-trivial sketches using String and math helpers compile and run without crashes. ✓
- First-run settings dialog — compiler path and project root saved to
app/settings.ini - New Sketch button — creates empty sketch in a new subfolder
- Recent Sketches button — last 5 paths persisted in
settings.ini - Speed slider — range 1–25 (= 0.1x–2.5x), passed to runtime as
speed_multiplier = 1/speed - Stop delay fix —
impl_delaysleeps in 10ms chunks, checksstop_requested_between each chunk -
Serial.available()/Serial.read()— UI text input feedsserial_buffer_, consumed by runtime - Switch component — toggles state on click, persists in
switchStates_QMap - Potentiometer component — drag up/down changes analog value 0–1023
- Two-column canvas layout — inputs left, outputs right, pin-aligned wiring
- Signal timeline — logic analyzer waveform view for digital pin state history
Milestone: Full interactive sketch workflow: open, edit, compile, run, adjust inputs, stop. ✓
-
pulseIn(pin, value, timeout)— fast path (distance sensor), color channel path (TCS3200), slow path (pin polling) -
delayMicroseconds— busy-wait with stop check -
analogWritefireson_pin_changedfor signal timeline tracking - Array-based pin detection (
const int PIN[N] = {...}) - Multi-pin component grouping (HC-SR04 → DistanceSensor, H-bridge → HBridgeMotor, TCS3200 → ColorSensor)
- Motor (H-bridge) separated from Servo (PWM single pin)
- Canvas sensor inputs — distance (cm → µs), color (R/G/B 0-255), analog (0-1023)
- Servo angle display — live °label updated from analogWrite value
-
Servoclass — injected inline bystrip_includes()replacing#include <Servo.h> - Preprocessor
strip_includes()step — runs beforereplace_api_calls(), handles library header replacement - Temperature, light, and generic analog sensor canvas inputs
- HBridge motor PWM pin detection and speed display
Milestone: Target benchmark sketch compiles and runs correctly. ✓
-
BoardProfilestruct insrc/core/runtime/boardprofile.h—name,chip,pin_count,analog_offset,analog_count,pwm_resolution,serial_count - Built-in profiles: Arduino Uno (ATmega328P), Arduino Nano (ATmega328P), Arduino Mega 2560 (ATmega2560), Arduino Due (AT91SAM3X8E), Teensy 4.1 (IMXRT1062)
- Board selector in Settings dialog — saved to
board/nameinsettings.ini -
RuntimeStatepin arrays bumped to fixed[80]/[20]max, all hardcoded20/14/8replaced with profile values -
inject_analog/impl_analogReaduseprofile.analog_offsetinstead of hardcoded14 -
CanvasWidgetfully profile-aware — pin loops, pin spacing,BOARD_H, servo angle, board name and chip label on canvas graphic -
setProfile()chain:SketchThread→SketchHost→ArduinoRuntime— board change propagates to running runtime - Unlocks running the full Lambo sketch on Teensy 4.1 without pin remapping
-
// @board <name>sketch hint —Preprocessor::extract_board_profile()reads the comment from raw source, surfaced viaCompileResult::board_hint, applied by MainWindow on run (canvas, label, runtime, and settings all update automatically)
- Linux shared library —
sketch.socompiled and loaded viadlopen/dlsym/dlclose - Platform-abstracted DLL lifecycle —
#ifdef _WIN32/#elseguards inSketchHost - Temp copy strategy consistent across platforms —
.tmp.dll(Windows),.tmp.so(Linux) - Linux compiler default —
/usr/bin/g++, detected and pre-filled in settings dialog - CMakeLists.txt links
dlon Linux, no extra libs on Windows - Build instructions for both platforms (CMake configure + build + run)
Milestone: Full compile-run-stop cycle verified on both Windows (MinGW) and Linux. ✓
Add a working 16x2 LCD to the canvas. Rudimentary visuals only — characters displayed in a fixed-width grid, no pixel-accurate graphics. Pretty rendering comes later in Phase 11.
How it works:
The preprocessor injects a replacement LiquidCrystal class in strip_includes() (same approach as Servo.h). The constructor stores the RS pin as the component identifier. lcd.print(), lcd.clear(), and lcd.setCursor() call api->lcd_print(rs, row, text) — a new entry in ArduinoAPI that fires a callback up through ArduinoRuntime → SketchThread → CanvasWidget, where QGraphicsTextItem labels on each row are updated in real time.
-
LiquidCrystalreplacement class injected bystrip_includes()—LiquidCrystal(rs, en, d4, d5, d6, d7), same approach asServo.h -
lcd.begin(cols, rows)— signals LCD active viadigitalWrite(rs, HIGH)and clears both rows -
lcd.print(const char*)/lcd.print(String)/lcd.print(int)/lcd.print(float)— all overloads callapi->lcd_print -
lcd.setCursor(col, row)— tracks current row for subsequentprint()calls -
lcd.clear()— clears both rows vialcd_print -
lcd_printAPI function — new entry at end ofArduinoAPIstruct;impl_lcd_printinArduinoRuntimefireson_lcd_printcallback - Qt signal chain —
on_lcd_print→emit lcdPrint(pin, row, text)onSketchThread→updateLcdText()slot onCanvasWidget - Canvas renders LCD as a cyan rectangle with two rows of
QGraphicsTextItem(Courier New 7pt, 16 chars wide), keyed inlcdRow0Labels_/lcdRow1Labels_by RS pin - CircuitDetector LCD detection — RS + EN + D4–D7 define group detected in
detect_multipin(); RS pin used as representative; other 5 pins claimed to prevent duplicate single-pin entries
Milestone: A sketch using
LiquidCrystalprints text and the canvas displays it correctly. ✓
Fill out the remaining commonly-used Arduino API surface and add low-level simulation realism. All items are self-contained runtime or preprocessor changes with no inter-dependencies.
Missing functions:
-
tone(pin, frequency)/tone(pin, frequency, duration)/noTone(pin)— buzzer/piezo support; no actual audio, just tracks state for canvas display -
attachInterrupt(pin, ISR, mode)—RISING,FALLING,CHANGEconstants added tovbnamespace; callback and mode stored inRuntimeState;impl_attachInterruptregisters the ISR andimpl_digitalWritefires it on matching pin transitions - ISR dispatch —
impl_digitalWritechecksRuntimeStatefor any ISR registered on the target pin after updating its state; if the transition matches the registered mode (RISING: LOW→HIGH,FALLING: HIGH→LOW,CHANGE: either), the ISR function pointer is called directly on the sketch thread;interrupts_enabled_is checked first and the call is skipped ifnoInterrupts()is active; the dispatcher temporarily setsinterrupts_enabled_ = falsebefore calling the ISR and restores it after, matching AVR's automatic cli/sei behaviour around interrupt execution; logically correct for rotary encoders, pulse counters, and interrupt-driven sensors even without cycle-accurate AVR timing -
ISR()vector macro transform — preprocessor scans forISR(X_vect) { ... }blocks before compilation; strips the AVR-specific macro wrapper, renames the function to__vb_isr_X_vect(), and injectsapi->register_isr("X_vect", __vb_isr_X_vect)calls intovb_setup();register_isrstores handlers inRuntimeState::isr_handlers_;avr/interrupt.handavr/io.hstripped silently; supported vectors and their simulation triggers:-
INT0_vect/INT1_vect→ pin 2 / pin 3 transition; dispatched fromimpl_digitalWrite -
PCINT0_vect/PCINT1_vect/PCINT2_vect→ pin-change group transitions; dispatched fromimpl_digitalWrite -
USART_RX_vect→ fires when the user sends input via the serial monitor (inject_serial) -
WDT_vect→ watchdog timeout in interrupt mode (rather than triggering a reset); coexists with theavr/wdt.hsimulation - Unknown vectors → surfaced as a warning: "ISR vector 'X_vect' is not simulated — the handler will never fire" rather than a silent compile failure
-
-
noInterrupts()/interrupts()— track enabled state inRuntimeState::interrupts_enabled_; preprocessor replaces calls withapi->prefixed versions -
EEPROM.read(addr)/EEPROM.write(addr, val)/EEPROM.update()— 1024-bytestd::array<uint8_t, 1024>inRuntimeState; bounds-checked (out-of-range returns0xFF);update()skips write if value unchanged;#include <EEPROM.h>stripped by preprocessor; no disk persistence between sessions -
Serial1/Serial2runtime — additional hardware UARTs on Mega 2560, Due, Teensy 4.1; same implementation asSerial, separate buffers and callbacks (on_serial1_output,on_serial2_output); preprocessor mapsSerial1.*/Serial2.*calls toapi->Serial1_*/api->Serial2_* -
Serial1/Serial2split monitor UI — when a board withserial_count > 1is active, the Serial monitor tab splits horizontally into labeled panes (Serial | Serial1 | Serial2); driven byserial_countonBoardProfile;SketchThreademitsserial1Output/serial2Outputsignals wired to the new monitor panes;rebuildSerialMonitors()rebuilds the tab when the board profile changes at runtime -
Serial.printf(format, ...)— common on ARM and ESP32 boards; injected via the preprocessor as an overload on theSerialobject; maps tosnprintfinto a stack buffer then callsapi->serial_print;#include <stdio.h>already available in the injected header
Missing libraries (preprocessor injection, same approach as Servo.h):
-
SoftwareSerial— injected class replacing#include <SoftwareSerial.h>; constructor storesrxPin/txPin;begin,print/println(4 overloads each),write(byte),write(buf, n),available,read,peek;listen/isListening/overflowreturn stubs; output routed to main serial monitor prefixed[SW:N]where N is the RX pin; RX buffer injectable per-pin viaArduinoRuntime::inject_soft_serial(rxPin, data);replace_token()preprocessor helper prevents variable names ending inSerial(e.g.mySerial) from being mis-rewritten by theSerial.*replacement pass - Library injection files — each injected library class lives in its own
.incfile insrc/core/build/libs/(servo.inc,liquidcrystal.inc,softwareserial.inc), embedded at build time the same wayinjected_header.incis;strip_includes()is a flat table of{header_name, const char* content}pairs and a single loop — adding a new injectable library = add one.incfile, embed it in CMake, add one entry to the table -
avr/wdt.h— watchdog timer simulation;wdt_enable(WDTO_Xs)starts a countdown timer inRuntimeState(timeout values: WDTO_15MS through WDTO_8S);wdt_reset()resets the countdown; if the timer expires before the nextwdt_reset()call, the simulation triggers a virtual reset (stops the sketch thread, clears runtime state, restarts fromvb_setup()) and surfaces a canvas message "Watchdog reset — wdt_reset() was not called in time"; when combined with sleep modes, watchdog expiry is the wakeup condition;wdt_disable()cancels the timer; injected header definesWDTO_*constants matching real AVR values -
avr/sleep.h— sleep mode simulation;set_sleep_mode(mode)stores the requested mode inRuntimeState(SLEEP_MODE_IDLE,SLEEP_MODE_PWR_SAVE,SLEEP_MODE_PWR_DOWN, etc.);sleep_enable()sets a flag;sleep_cpu()blocks the sketch thread on a condition variable — the thread suspends and the canvas shows a "Sleeping…" indicator; wakeup sources release the condition variable: watchdog timer expiry (any sleep mode) or ISR dispatch firing on a pin configured withattachInterrupt(modes that support pin-change wakeup);sleep_disable()clears the flag; covers the common pattern ofwdt_enable→sleep_cpu()→ periodic wakeup used in battery-powered data loggers and low-power sketches
Missing sketch structure:
- Multi-file sketch support — if a sketch folder contains
.hor additional.cppfiles, include them in the compile pass;strip_includes()must pass through#include "localfile.h"rather than stripping it - Safety delay injection in
whileloops —inject_while_delays()scans everywhile(...) { }body and injectsapi->delay(1)if no delay is present; skipsdo...whiletails and bodies that already have delays; tight sensor-polling loops no longer freeze the simulation thread -
F()macro compatibility —F("string")is used in a large proportion of real sketches to store string literals in AVR flash; in VEMCODE on x86 there is no flash distinction, soF(x)should be defined as(x)in the injected header; without this, any sketch usingF()fails to compile with a cryptic error - Inline AVR assembly transform —
transform_asm_blocks()runs early in the pipeline; handles__asm__,asm, with or without__volatile__/volatile, with or without constraint strings:-
__asm__("nop")→ stripped silently -
__asm__("cli")→api->noInterrupts() -
__asm__("sei")→api->interrupts() -
__asm__("sleep")→ stripped with note -
__asm__("wdr")→ stripped with note -
__asm__("rjmp 0")→ stripped with note - Unrecognized instruction → stripped with warning: "Unrecognized assembly instruction 'X' removed"
-
-
PROGMEMkeyword compatibility —const char text[] PROGMEM = "..."is common in real sketches for flash storage;PROGMEMis an AVR-specific GCC attribute that doesn't exist on x86; define it as empty (#define PROGMEM) in the injected header so sketches using it compile without errors -
#ifdef ARDUINO/#ifndef ARDUINO— common pattern in cross-platform sketches that lets code detect whether it's running on real hardware; VEMCODE doesn't defineARDUINOso the wrong branch compiles; fix is one line in the injected header:#define ARDUINO 100(matching the value the real Arduino IDE defines) -
pgm_read_byte/pgm_read_word/pgm_read_dword/pgm_read_float— plain pointer dereferences in the injected header;#include <avr/pgmspace.h>stripped silently -
<util/delay.h>—#define F_CPU 16000000UL,#define _delay_ms(ms) api->delay(...),#define _delay_us(us) api->delayMicroseconds(...);#include <util/delay.h>stripped silently -
analogReference(DEFAULT/INTERNAL/EXTERNAL)— stubbed as a no-op in the injected header
Error UX:
- Humanized compiler errors — post-process raw g++ output before display; a regex rewrite table maps common cryptic patterns to plain-English messages:
'X' was not declared in this scope→"'X' not found — did you forget to declare it?"no matching function for call to 'X'→"Wrong arguments passed to X"expected ';' before '}'→"Missing semicolon, probably the line above"expected '}' at end of input→"Unclosed brace — one of your { was never closed"lvalue required as left operand of assignment→"Did you mean == instead of =?"undefined reference to 'X'→"Function 'X' is used but never defined"control reaches end of non-void function→"Function is missing a return statement"expected unqualified-id before '{'→"Code found outside a function — all code must be inside setup(), loop(), or another function"too many/few arguments to function 'X'→"Wrong number of arguments passed to 'X'"stray '\' in program→"Invalid character in code — this sometimes happens when copy-pasting from a website; try retyping the line"overflow in implicit constant conversion→"Number is too large for this variable type — try using long instead of int"comparison between pointer and integer→"Can't compare strings with == — use strcmp() or the String class"
- No-components-detected hint — after
CircuitDetector::detect()runs, ifcomponents_is empty (or contains only a Serial entry), the reason matters and the message should reflect it; three distinct cases:- Pin definitions found but names not recognized as component keywords (e.g.
#define MY_OUTPUT 5) → "Pin definitions found but couldn't identify component types — try descriptive names likeLED_PIN,SERVO_PIN,BUTTON_PIN" - Hardcoded pin numbers used with no defines at all (e.g.
digitalWrite(5, HIGH)) → "Pin numbers are hardcoded — give them names likeconst int LED_PIN = 5;so the simulator can identify them" - Pin definitions exist only in an included local header (e.g.
#include "config.h"has the#defines) —CircuitDetectorcurrently only scans the main.cpp; extend it to also scan local.hfiles pulled in by the sketch, or surface: "No components detected — if your pin definitions are in a header file, try moving them into the main sketch" - No pin usage detected at all → existing generic message
- Pin definitions found but names not recognized as component keywords (e.g.
- Unsupported
#includewarning — after known headers are replaced, any remaining#include <X.h>generates a named warning in the serial monitor before compile: "WARNING: <Wire.h> is not supported by VEMCODE — calls to this library will not work" - Missing
setup()/loop()— regex-checked before invoking g++; surfaces "Sketch is missing a setup() function" / "…loop() function" instead of a wall of linker errors - Pin out of range for selected board — if a
const intor#definepin value exceeds the active board's pin count, warn: "Pin 50 is not available on the Arduino Uno (max pin 13)" -
analogWrite()on a non-PWM pin — cross-referenceanalogWritecall sites against the board profile's PWM pin list and warn: "Pin X does not support PWM on the selected board — analogWrite() will have no effect" - Same pin claimed by two components — when
CircuitDetectorwould silently drop a duplicate, instead surface: "Pin X is used by both [Component A] and [Component B] — only one will be simulated" -
// @boardhint unrecognised — ifextract_board_profile()finds a// @boardcomment but the name doesn't match any known profile, warn: "Unknown board 'X' in @board hint — using currently selected board instead" -
map()with equal min/max — static check formap(val, x, x, ...)or runtime divide-by-zero guard inimpl_map; surface "map() called with min == max — this causes a division by zero" instead of a silent crash - Sketch thread crash wrapper — wrap the sketch execution loop in a try/catch and install a SIGFPE/SIGSEGV handler so any unhandled exception, division by zero, or out-of-bounds crash surfaces "Sketch crashed — check for division by zero or out-of-bounds array access" instead of a silently frozen canvas
-
delay()inside ISR callback — static check: if adelay()call appears inside a function registered viaattachInterrupt(), warn "delay() inside an interrupt handler will hang on real Arduino — interrupts are disabled during ISR execution" -
digitalPinToInterrupt(pin)defined in injected header asinline int digitalPinToInterrupt(int pin) { return pin; }so sketches using it correctly compile without error - Pin defined as an expression —
#define LED_PIN (2+1)orconst int LED_PIN = BASE + 3;compiles and runs fine butCircuitDetectorcannot evaluate the expression and silently misses the component; detect when a pin define contains operators or references another variable and warn: "Pin 'LED_PIN' is defined as an expression — the simulator could not evaluate it and the component may not appear on the canvas; use a plain number instead"
Simulation accuracy warnings (patterns that work in VEMCODE but fail on real hardware):
- Missing
volatileon ISR-shared variables — if a variable is written inside anattachInterruptcallback and read inloop()orsetup(), warn: "'X' is shared with an ISR but not declared volatile — this may work in simulation but will likely fail on real hardware" -
String +=in a tight loop — ifStringconcatenation is detected insideloop()with no apparent upper bound, warn: "Repeated String concatenation in loop() causes heap fragmentation on real Arduino — consider using a char buffer instead" -
pinMode()never called for adigitalWrite()pin — if a pin appears in adigitalWrite()call but has no correspondingpinMode(pin, OUTPUT), warn: "Pin X is used with digitalWrite() but never set as OUTPUT via pinMode() — it will default to INPUT on real hardware"
Simulation realism:
- Floating pin simulation — undriven INPUT pins return random HIGH/LOW
- Button bounce simulation — rapid toggles on click before settling (~10ms);
TACT/CLEAN/IDEALprefix gives aButtonCleancomponent with no bounce - Optional gaussian noise on analog readings (off by default, toggle in Settings dialog)
Milestone: Simple sketches using timers, interrupts, EEPROM, and additional serial ports run correctly; the simulation behaves realistically on common hardware edge cases. ✓
Pull the component plugin architecture forward so that all new components added in this phase and beyond use the new system from day one. The dev component generator is built last, on top of the stable plugin foundation.
Implementation order: foundation → component migration → detector/canvas refactor → new components → generator.
Step 1 — Foundation:
-
ComponentEventTypeenum insrc/core/circuit/componentitem.h— typed events that input components emit upward:DigitalPress(int 0/1),BouncePress(int 0/1),AnalogValue(int 0–1023),PulseUs(qulonglong microseconds),ColorRGB(QVariantList {r, g, b, s2_pin, s3_pin}); new Phase 8 components add entries to this enum as needed -
ComponentItembase class insrc/core/circuit/componentitem.h/.cpp— inheritsQGraphicsObject; pureboundingRect()andpaint(); virtualonPinChanged(int value)(no-op default, overridden by output components); virtualupdateText(int row, const QString& text)(no-op default, overridden by LCD);Q_SIGNAL void inputChanged(int pin, int eventType, QVariant value)(emitted by input components from their own mouse event overrides); storespin_set in constructor -
ComponentDefinitionstruct insrc/core/circuit/componentregistry.h— holdstype_name,detect_singlekeyword list,detect_multipin-role map,detect_patternsource pattern list,is_outputflag, andcreate_itemfactory (std::function<ComponentItem*(int pin, QGraphicsItem*)>);DetectedComponentincircuitdetector.hswitches fromComponentType type(enum) tostd::string type_name; theComponentTypeenum is deleted entirely —CanvasWidgetand all callers look up bytype_namestring from this point forward -
ComponentRegistrysingleton insrc/core/circuit/componentregistry.cpp— flatstd::vector<ComponentDefinition>;register_component()called from each component file's static initializer;find_by_type()used byCanvasWidget -
CMakeLists.txtupdated to globsrc/components/*.cpp— new component files added by dropping a file, no CMake edits needed
Step 2 — Component migration (one file per component in src/components/):
-
led.cpp— output;onPinChangedsets active/inactive color; keywords:LED,LAMP,DIODE,INDICATOR(unambiguous output-only terms;LIGHTbelongs to analogsensor only) -
button.cpp— input; overridesmousePressEvent/mouseReleaseEvent, emitsBouncePress; keywords:BUTTON,BTN,TACT,PUSH;ButtonCleanvariant (CLEAN,IDEALprefix) emitsDigitalPressinstead -
switch.cpp— input; click toggles latched state, emitsDigitalPress; keywords:SWITCH,TOGGLE,RELAY -
buzzer.cpp— output;onPinChangedshows active indicator; keywords:BUZZER,PIEZO,SPEAKER,BEEPER -
servo.cpp— output;onPinChangedupdates angle label; detect pattern:.attach(; keywords:SERVO -
potentiometer.cpp— input; overridesmouseMoveEventfor drag, emitsAnalogValue; keywords:POT,POTENTIOMETER,KNOB,DIAL -
analogsensor.cpp— input; text field, emitsAnalogValue; keywords:LIGHT,LDR,PHOTO,TEMP,TEMPERATURE,NTC,SENSOR(generic fallback) -
distancesensor.cpp— input; text field (cm → µs), emitsPulseUs; detect pattern:pulseIn(paired with trig/echo timing; keywords:TRIG,ECHO,DISTANCE,ULTRASONIC,SONAR,HCSR -
hbridgemotor.cpp— output;onPinChangedupdates speed/direction label; multi-pin role map (PWM, CWISE, ANTI_CWISE); keywords:MOTOR,HBRIDGE,ENA,IN1 -
colorsensor.cpp— input; R/G/B text fields, emitsColorRGB; multi-pin role map (OUT, S2, S3); detect pattern:pulseIn(on a pin withS2/S3siblings; keywords:COLOR,TCS,S2,S3 -
lcd.cpp— output;onPinChangednot used; overridesupdateText(int row, const QString& text)to update its own row labels;CanvasWidget::updateLcdText(int pin, int row, const QString& text)stays as a public method but routes throughpinItems_[pin]->updateText(row, text)instead of separate QMaps; detect pattern:LiquidCrystal; multi-pin role map (RS, EN, D4–D7); RS pin is representative
Step 3 — Detector and canvas refactor:
-
CircuitDetectorrefactored to loop over the registry — detection runs in three confidence tiers: (1)detect_patternsource patterns first, (2)detect_multipin-role matching, (3)detect_singlekeyword matching as final fallback; a match at a higher tier short-circuits the lower tiers; no component-specific knowledge remains inCircuitDetectoritself-
detect_singletier —infer_type()replaced withComponentRegistry::find_by_single_keyword(), looping registered components'detect_singlelists instead of a hardcoded keyword ladder; fixed the LightSensor/TempSensor/LED-vs-LIGHT drift bugs this exposed along the way -
detect_multitier — needs a generic grouping engine covering the four correlation strategies currently hardcoded per component: suffix-correlate (HC-SR04:TRIGPIN1/ECHOPIN1share suffixPIN1), prefix-correlate (HBridgeMotor:MOTOR1_PWM/MOTOR1_CWISEshare prefixMOTOR1), array-correlate (ColorSensor: five same-length arrays matched by index), singleton (LCD: assumes one instance, grabs one of each role globally) -
detect_multibecomes an ordered list of (role, keywords) pairs onComponentDefinitioninstead ofstd::map— amapiterates alphabetically, which silently produces the wrongpins[]order (e.g.ANTI_CWISE<CWISE<PWM) once something actually reads it; toucheshbridge_motor.cpp,color_sensor.cpp,lcd.cpp -
detect_patterntier — dispatches by pattern shape:.method(patterns (Servo.attach() matchobj.method(pin)directly; plainfunc(patterns (pulseIn() generalize the old PING wrapper-function search (find a user function containing the pattern, then resolve pins from its call sites) - LCD's
LiquidCrystal lcd(RS, E, D4, D5, D6, D7)ctor-arg fallback generalized into a reusable "extract N args from aClassName var(...)call" extractor, driven bydetect_multi's role count/order — not an LCD-specific regex
-
-
CanvasWidget::refresh()callsregistry.find_by_name(comp.type_name).create_item(pin, nullptr)for each detected component —scene_->addItem(item)andconnect(item, &ComponentItem::inputChanged, this, &CanvasWidget::onComponentInput)is the entire per-component setup; no per-type switch blocks -
CanvasWidget::updatePin()callsitem->onPinChanged(value)— items update their own visuals;pinItems_map changes type fromQGraphicsRectItem*toComponentItem*; all per-type QMaps (servoLabels_,lcdRow0Labels_,motorStates_, etc.) move into the component items themselves - All mouse handling removed from
CanvasWidget::mousePressEvent/Release/MoveEvent— input components handle their own events viaQGraphicsObjectmouse overrides;CanvasWidgetmouse overrides deleted or reduced to scene fallthrough only -
CanvasWidgetgains one forwarding slotonComponentInput(int pin, int eventType, QVariant)that re-emitsinputChangedup toMainWindow— the only signalCanvasWidgetexposes for component interaction -
MainWindowrefactored to oneonComponentInput(int pin, int eventType, QVariant)slot with a switch onComponentEventType— dispatches to the correctsketchThread_->inject_*call; all per-component signal/slot pairs removed
Step 4 — New simple components:
- RGB LED — three PWM pins (R, G, B);
onPinChangedblends channel values into a colored circle; detected from#definepin names containingRED/GREEN/BLUEas a group - Rotary encoder — two digital pins (CLK/DT) plus optional button; canvas shows a turn counter; pairs naturally with
attachInterrupt; keywords:CLK,DT,ENCODER,ROTARY - Infrared sensor - One digital pin (OUT); canvas shows a toggle switch to activate and deactivate the IR sensor; keywords:
IR_SENSOR,IR,INFRARED,IR_OUT
Step 5 — New complex components:
- Joystick — two analog axes (X/Y, 0–1023) plus a digital button; canvas shows dual sliders and a clickable button; emits
AnalogValueper axis andDigitalPressfor the button; keywords:JOYSTICK,JOY,VRX,VRY - Stepper motor — step count and direction tracked from STEP/DIR or IN1–IN4 pin patterns; canvas displays a position counter and rotation indicator; keywords:
STEP,DIR,STEPPER - Keypad matrix — 4×4 or 4×3; detected from
rowPins[]/colPins[]arrays (orROW1../COL1..define groups) plus aKeypadusage guard; real injectedKeypad.h-equivalent class does actualpinMode/digitalWrite/digitalReadrow scanning (CircuitDetector::detect_keypad_matrix,src/core/build/libs/keypad.inc); clickable grid on canvas with the real 4x4/4x3 membrane-keypad silkscreen layout; keywords:ROW,COL,KEYPAD - DHT11 / DHT22 — temperature and humidity;
#include <DHT.h>stripped and replaced with injected class (src/core/build/libs/dht.inc);dht.readTemperature()/readHumidity()return canvas-injected values via newArduinoAPIfloat hooks; canvas shows a color-sensor-style box with temperature + humidity input fields (src/components/dht.cpp); detected via a dedicatedCircuitDetector::detect_dht(theDHT dht(DHTPIN, DHTTYPE)constructor's 2nd arg is a type selector, not a pin); keywords:DHT,DHTPIN,DHT_PIN
Step 6 — New display components:
- 7-segment display — single and multi-digit, segment-accurate rendering
- MAX7219 LED matrix —
LedControl.hinjection (src/core/build/libs/ledcontrol.inc) buffers rows locally and flushes each through a new dedicatedmatrix_set_row(pin, row, bits)runtime hook (keyed by CS pin), same shape as the LCD'slcd_printhook; renders an 8×8 grid toggled bysetLed/setRow/setColumn;setIntensity/shutdownstubbed; CS/CLK/DIN multi-pin role map via bothPrefixand bareSingletonstrategies (src/components/max7219.cpp); canvas renders as a 100x100 square (matching other components' long side) with a circle dot grid rather than the standard rectangle; single device only, no daisy-chain - Basic OLED — text and simple graphics (SSD1306-compatible);
Adafruit_SSD1306.hinjection (src/core/build/libs/ssd1306.inc),Adafruit_GFX.h/Wire.haccepted alongside it; self-contained framebuffer class (1 byte/pixel, up to 128x64) implementsdrawPixel/drawLine/drawRect/fillRect/drawCircle/fillCircle/drawBitmap/print/printlndirectly against the buffer, then flushes the whole thing through a newoled_display(pin, pixels, width, height)runtime hook once perdisplay(), same buffer-then-flush shape as NeoPixel'sshow(); text uses a small hand-authored 3x5 dot-matrix font (legible approximation, not a real hardware font); detected viaCircuitDetector::detect_oledparsingAdafruit_SSD1306 display(width, height[, &Wire, resetPin])— since I2C has no dedicated GPIO pin (unlike every other component so far), the canvas item keys offresetPinwhen given, or a fixed sentinel pin (900, matched by both the detector andAdafruit_SSD1306::NO_RESET_PIN_KEY) when it's-1, which is the common case for breakout modules with no RST line; renders as a scaled bitmap (src/components/oled.cpp, ~1.5x real pixel size) rather than the fixed 100px-wide footprint every other component uses, another MAX7219-style sizing exception;OLED/SCREEN/DISPLAYkeywords moved offlcd.cpp's fallback list onto this component's, since a bare#define OLED_PINsketch should now fall back to a blank OLED rather than an LCD - NeoPixel / WS2812B strip — individually addressable RGB LEDs, single-pin protocol, configurable strip length;
Adafruit_NeoPixel.hinjection (src/core/build/libs/neopixel.inc) buffers pixel colors locally and flushes the whole strip through aneopixel_show(pin, rgb, count)runtime hook once pershow(), matching the real library's buffer-then-flush semantics rather than per-pixel MAX7219-style flushing; detected from theAdafruit_NeoPixel strip(count, pin[, type])constructor (CircuitDetector::detect_neopixel), same "read the constructor call" pattern as MAX7219/DHT; renders as a wrapping dot grid that grows downward with pixel count (src/components/neopixel.cpp), capped at 256 pixels; color-order/speed flags (NEO_GRB/NEO_KHZ800) accepted for source compatibility but unused
Milestone: All existing components registered through the plugin system with three-tier detection;
CircuitDetectorandCanvasWidgetcontain no per-component knowledge; adding a new component is a single self-contained file; RGB LED, rotary encoder, joystick, keypad, DHT, 7-segment, OLED, and NeoPixel sketches all run on the canvas.
Heavier runtime work requiring more architectural changes: bus protocol simulation, virtual device responses, and direct register access.
Protocol libraries (preprocessor injection + virtual device responses):
-
Wire.begin/Wire.write/Wire.read— byte-level I2C simulation; no electrical bus characteristics; device responses come from the virtual I2C device panel - Virtual I2C device panel — "Devices" tab in the debug panel; table of 7-bit address → response byte sequence; when the sketch calls
Wire.requestFrom(addr, n), the runtime looks up the address and returns the configured bytes; entries are editable at runtime; covers the common pattern of reading a sensor register:Wire.beginTransmission→Wire.write(reg)→Wire.endTransmission→Wire.requestFrom→Wire.read() -
SPI.begin/SPI.transfer— byte-level SPI simulation; no electrical bus characteristics; also stubsbeginTransaction/endTransaction/SPISettingsas no-ops for real-world sketch compatibility; unlike Wire there's no address field, so device responses come from a single configurable byte sequence in the "SPI" tab thattransfer()cycles through on every call
Low-level AVR simulation:
- AVR GPIO register simulation —
DDRB,PORTB,PINB,DDRC,PORTC,PINC,DDRD,PORTD,PINDas overloaded-operator structs in injected header; ATmega328P (Uno/Nano) port layout only; reads/writes map to the same pin state asdigitalWrite/digitalRead/pinModeby routing through those sameapi->calls per affected bit; bit-mask operations (DDRB |= (1 << PB5),PORTB |= (1 << PB5)) work correctly; also supports the real AVR quirk of writing toPINxto toggle the correspondingPORTxbit - AVR hardware timer register simulation —
TCCR1A,TCCR1B,OCR1A,OCR1B,TIMSK1,TCNT1etc. as overloaded-operator structs; writes toOCR1A/OCR1Bupdate the corresponding pin's PWM duty cycle via the existinganalogWritepath;TIMSK1overflow and compare-match interrupt enable bits register callbacks inRuntimeStatethat fire on the simulated timer tick; covers sketches that configure hardware PWM or use Timer1/Timer2 for precise timing without callinganalogWritedirectly-
TIMER1_OVF_vect/TIMER2_OVF_vect→ timer overflow; dispatched from the simulated timer tick when overflow interrupt enable bit is set inTIMSK1 -
TIMER1_COMPA_vect/TIMER1_COMPB_vect→ timer compare-match A/B; dispatched whenTCNT1reachesOCR1A/OCR1B
-
Milestone: Sketches using I2C/SPI sensor libraries compile and run; direct GPIO register writes and hardware timer configuration work correctly. ✓
Polish the editor into a first-class coding environment, consolidate settings, add a serial plotter, and give the canvas a proper layout system.
Editor:
- Code completion — Ctrl+Sift+Space shows a filtered popup of Arduino API functions plus all functions, variables, and
#defineconstants declared in the current sketch - Member-aware dot completion — typing
.right after a known object immediately pops up just that object's member names (no idle wait); covers the fixed globals (Serial/Serial1/Serial2,Wire,SPI,EEPROM) directly by name, plus user-declaredLiquidCrystal/Servo/SoftwareSerialvariables via a declaration scan (SketchLinter::scanDeclaredTypes()) that maps variable name → type before matching against a per-type member list - Find & Replace — Ctrl+F opens an inline find bar; Ctrl+H adds a replace field; Enter steps through matches, Escape dismisses
- Save in-place — Ctrl+S saves silently to the current file path when a sketch is already open; only prompts for a name on first save of a new unsaved sketch
- Autosave / crash recovery — editor content written to a
.autosavefile in the sketch folder every 30 seconds; on next open, if an.autosavefile is newer than the.cppfile, offer to restore it; file is deleted on a clean save or close - Unsaved changes indicator — append
*to the window title when the editor content differs from the saved file; clear it on save - Auto-close brackets — typing
(,[,{, or"inserts the matching closer and positions the cursor inside; typing the closer when it is the next character skips over it instead of doubling - Bracket matching — when the cursor sits adjacent to
(,),{,},[, or], highlight the matching bracket - Comment toggle — Ctrl+/ adds
//to the current line or selected lines; pressing again removes it - Font size zoom — Ctrl+
+/ Ctrl+-/ Ctrl+scroll adjusts the editor font size; resets to default with Ctrl+0 - Duplicate line — Ctrl+D copies the current line and inserts it on the line below
- Compile warnings — compiler warnings surfaced in the editor alongside errors; yellow line backgrounds for warning lines with corrected line numbers
- Sketch templates — "New Sketch" dialog offers built-in starters (Blink, Button, Serial Echo) read from
app/sketches/templates/manifest.json; selected template's content copied into the new sketch folder - Example sketch library — "Open Example..." dialog groups
app/sketches/examples/manifest.jsonentries by component type (LED, Servo, LCD, Distance Sensor); selecting one prompts for a name and opens it as a new sketch ready to run - In-app Arduino API reference — right-click a known function in the editor for a popup showing its signature, parameter descriptions, and return value; covers the core global API plus library methods unique enough to identify without ambiguity (
src/ui/editor/apireference.h)
Serial Plotter:
- Numeric values printed via
Serial.println()graphed over time in a scrolling plot panel (SerialPlotter, new "Serial plotter" tab in the debug panel); multiple named variables supported vialabel:valuetokens separated by whitespace/commas, matching the Arduino IDE Serial Plotter protocol; unlabeled bare numbers default to "Value"/"Value 2"/...; auto-scaled shared Y axis, scroll/zoom via mouse wheel (Ctrl+wheel to zoom) same as the signal timeline
Settings panel:
- Compiler path — auto-detect common g++ install locations on first run (MinGW on Windows,
/usr/bin/g++on Linux); show a validation indicator (green tick / red cross) next to the path field - Component configuration — CTRL+click any canvas component to open a config dialog for parameters that are invisible to the sketch itself (not things like NeoPixel strip length / keypad matrix size / MAX7219 device count, which are already read from the sketch's own code): MAX7219 rotation (0/90/180/270, physical mounting orientation), 7-segment common-cathode vs. common-anode polarity (currently hardcodes common-cathode, a real common-anode sketch renders inverted), RGB LED common-cathode vs. common-anode polarity (same inversion problem, one level up from single-channel), base LED color (currently one fixed hardcoded color regardless of the real LED's actual color, purely cosmetic); values saved to the
.vblayoutfile alongside position - App theme — "Dark theme" toggle in Settings; one
qApp-wide stylesheet re-themes the toolbar, panels, editor (incl. syntax highlighting), tables, and signal timeline live. Canvas board/chip/pin chrome and every component's own colors are fixed in both themes on purpose (matches wires/component identity colors) - Auto-compile on save — toggle in settings; when enabled, saving immediately triggers a compile without a manual Run click; separate keybind for run (Ctrl+R)
- Default sketch location — configurable root folder for new sketches; New Sketch, Open, and Save As all use it; GUI and headless CLI share the same
sketches/default_locationsetting, defaulting to the old<app>/sketchespath - Change Keybinds — Keybinds tab in Settings; remap Save, Save As, Run, editor/canvas zoom, Find, code completion, duplicate line, and comment toggle; conflicts blocked on save, changes apply live
Canvas improvements:
- Canvas layout mode — "Layout" toolbar button, components become draggable
- Positions saved to
sketch_name.vblayoutnext to.cppfile; use saved positions on load, auto-generate otherwise - Canvas zoom — Alt+= / Alt+- to zoom in/out; zoom level saved per sketch in the
.vblayoutfile
Milestone: The editor feels complete for day-to-day sketch writing; serial plotter graphs live data; canvas layout can be saved and restored; bit-banged protocols are decoded in the signal timeline.
Run any number of sketches simultaneously in one window, each in its own tab, rather than one board per window or one board per app instance.
One window, tab bar, thin toolbar: a tab bar sits under the toolbar and above the three panels — sketch1 | sketch2 | ... | sketchN. Clicking a tab swaps the entire editor/canvas/debug/serial scene to that sketch. + opens any sketch file into a new tab, not just variations of the current one. Each tab keeps running in the background when not focused — switching tabs only changes what's rendered, it never pauses the non-focused sketch's thread, which is what makes "simultaneous" actually true rather than just "switchable."
-
SketchSession— new class bundling everythingMainWindowcurrently owns directly for one sketch (codeEditor_,highlighter_,canvasWidget_,sketchThread_/SketchHost/ArduinoRuntime, debug panels, serial monitor).MainWindowshrinks to: toolbar + tab bar + aQStackedWidgetofSketchSessions - Tab bar UI —
+to open any sketch file into a new tab; closing a tab stops that session's thread (decide: does closing while running warn first, or just stop silently) - Toolbar reflects the focused tab's own run state, not a single global flag — each
SketchSessiontracks its own running/stopped state; switching tabs re-syncs the Run/Stop button to whichever session is now focused, independent of what any other tab is doing (e.g. stop sketch3, switch to still-running sketch2, button must say Stop for sketch2) - Background tabs keep their
SketchThreadrunning while not focused — hiding a widget in Qt doesn't pause its thread, so this should fall out of theSketchSessionsplit rather than needing new scheduling logic - Bridge component — a small user-placed canvas component (configured like the Wire/SPI virtual device panels, not auto-detected from source) representing a connection to another open tab; drawn with real wires into it from the relevant pins (e.g. TX/RX), labeled "Bridge to sketchN"; data actually moves via the existing
inject_serial()mechanism (board A'son_serial_outputalso calls board B'sinject_serial()) — no new communication path, just a canvas-visible endpoint for a link that can't be drawn as one continuous wire since the two tabs are never on-screen at the same time - Thread-safe state injection — replace
pin_values,analog_values, andpwm_valuesarrays inRuntimeStatewithstd::atomic<int>. Note: eachSketchSession'sRuntimeStateis already independent (no cross-session race exists), so this isn't strictly required by multi-board support itself — it's hardening the pre-existing GUI-thread-vs-sketch-thread race that already exists in the single-sketch case today, just worth doing now since there's more thread surface area to get right - Enables master/slave, sensor node + controller, and I2C peripheral sketches
Milestone: Three sketches open in three tabs, all running simultaneously (confirmed by each tab's Run/Stop button independently reflecting its own state); two of them communicate over a bridge component and virtual Serial with both canvases updating correctly when focused.
Tag
v1.0-betaafter Phase 10 is complete. The tool is feature-complete enough for real-world use: the editor is polished and the API surface covers the common cases. Installer and auto-update (QtIFW + GitHub Releases as update repository) ships with the stablev1.0release — not before.
- Add/Remove component button — detached window: dropdown + pin picker + define name, injects
#define/pinMode()into the sketch. Hardest part: safely inserting into a hand-edited sketch without duplicating defines or disturbing otherpinModecalls — v1 should be one-shot insert-only, anchored by a generated comment marker per component (e.g.// VEMCODE:BTN1), not full bidirectional canvas↔text sync - Step-through debugger — clickable gutter breakpoints, Step/Resume buttons;
impl_vb_breakpointblocks the sketch thread on a condition variable (same pattern asimpl_sleep_cpu); variable watch and canvas already show paused state for free. Hardest part: needs a real line-boundary scanner (brace/paren-depth tracking, skip string/comment contents) to inject breakpoints safely — everything else in the preprocessor today is narrow regex, not real parsing - Installer — QtIFW with GitHub Releases as the update repository; bundle MinGW for zero-dependency install on Windows; package for common Linux distros
- macOS support
- Additional board profiles (STM32, etc.) — add one
BoardProfileentry each - Component visual upgrades — QPainter-drawn visuals (LED glow, buzzer pulse, motor rotation) replacing placeholder rectangles, animated off a shared canvas
QTimerphase value; architecture is SVG-ready, swapping toQSvgRendererlater is a single-filepaint()change per component. Blocked on a friend doing the actual graphics — timeline unknown - Memory analysis — flash/SRAM usage via
arduino-cli compile --format json(skips raw avr-gcc, which lacks the Arduino core); blocks Run over a board's limit, warns on heap usage tracked separately in VEMCODE's own runtime; memory bar in UI,.hexexport - Hardware Bridge — per-pin mixing of virtual and real: some pins stay canvas-driven, others wire to a real board over USB serial, same running sketch, no code changes either way (e.g. a virtual OLED/button UI paired with a real sensor under test). Native desktop app avoids the WebSerial/browser-permission friction a webUI sim would have doing the same thing
- MicroPython / CircuitPython support — Python execution path on Pico and compatible boards using the same runtime, canvas, and signal timeline
- ESP32 + Network Simulation — WiFi stubs and a mock HTTP server; deterministic, offline, and repeatable responses for firmware testing. Must avoid real sockets entirely (no actual
bind/connect/listento a real port) to preserve VEMCODE's "no network code" trust claim — same virtual-panel pattern as Wire/SPI: a configurable fake response the sketch reads, not an actual HTTP client/server - Signal timeline protocol decoder: The signal timeline already records every
(timestamp_µs, pin, level)transition — the same data a logic analyzer captures. Add a decoder layer that runs over this stream. - RTC module (DS1307/DS3231) — another I2C device with no dedicated GPIO pin, same shape as the OLED; a good test of whether the synthetic-pin-key pattern (
Adafruit_SSD1306::NO_RESET_PIN_KEY) actually generalizes to a second device or was an OLED-specific workaround - Accelerometer/Gyroscope (MPU6050) — another I2C component, but a different interaction shape than RTC/OLED (a live 3-axis value the user manipulates, not read-only or a framebuffer)
- Document headless +
.timelineas a CI workflow — the pieces already exist (headless CLI,.timelineASSERT/action fixtures, exit-code pass/fail); this is packaging/docs work (a GitHub Actions example, maybe a minimal Docker image with the compiler toolchain) to let users run their own sketch's logic tests in CI, not new engine code - Multi-file sketch editing — distinct from Phase 11's multi-board tabs: a sketch spanning
sketch.cppplus a companion.h/.cppalready compiles (multi-file sketch support), but the editor has no UI to view/edit those companion files; add tabs within one sketch project for its own additional files - Canvas export (PNG/SVG) —
QGraphicsScene::render()to aQImage/QSvgGeneratoris a small, self-contained addition; useful for docs, tutorials, forum posts, and bug reports without needing an external screenshot tool - Colorblind-accessible component/wire identification — Phase 10 deliberately fixed wire and component colors across both themes to "match wires/component identity colors," which makes color the only signal distinguishing things like RGB LED channels or multi-pin wiring today; add a pattern/label fallback so that's not a hard accessibility wall