Releases: Sydney680928/MogwaiNano
Release list
MOGWAI NANO v0.3.0
Added
-
makeData— creates aMOGDataof a given size, filled with a given byte value (e.g.1024 0 makeDatafor 1024 zero bytes,1023 0xFF makeDatafor 1023 bytes all set to0xFF), without pushing each byte onto the stack first. Building a large buffer viarepeat { 0 } ->dataallocates oneMOGObjectper byte before conversion, which can exhaust memory on lower-RAM devices even though it works fine on more capable ones;makeDataavoids that transient peak entirely -
nano.send— sends an arbitrary string to the connected device:"TIME=15:45" nano.send. The device fires aSTUDIO_DID_SENDevent on the currently running program, with the received string available aseventData(a plainMOGString), letting a long-running program (forever do { ... }) react to free-form commands from Studio without needing to be stopped and relaunched. No built-in message format is imposed — parsing the string (e.g. aNAME=valueconvention) is entirely up to the receiving script -
->vars,->safeVars,->params— ported with 100% functional parity from the desktop MOGWAI engine.->varsextracts values from a record or the stack straight into matching local variables, with no type checking beyond raising an error if the stack doesn't have enough elements.->safeVarsdoes the same but also validates each value's type against a declared record, and is whatto ... with [...] douses automatically for typed function parameters.->paramsvalidates a named-parameter record (with optional default values) against a declared shape, raising an error if a required parameter is missing or mistyped, and silently ignoring extras -
Bitwise operators on
.number—&(AND),|(OR),^(XOR),~(invert),<</>>(shift left/right). Note the context-sensitive parsing of&:&Aright before a name is still the existing reference sigil, whileX Y &after two numbers already on the stack is the bitwise AND primitive — the two are distinguished by position, not a separate symbol. No.binary/B:type was introduced for this — these operators work directly on regular numbers, which was enough for the intended use cases (e.g. manipulating individual pixels in a display frame buffer) without the overhead of a whole new type family -
floor,mod— standard floor and modulo, useful alongside the new bitwise operators for address/offset arithmetic (e.g. computing a byte offset and bit position from pixel coordinates) -
Engine.Idle()— a cooperative yield point called fromMOGCode.Execute()on every executed item (both inside and outside loops), callingThread.Sleep(0)every 10 iterations. The nanoCLR schedules threads cooperatively rather than preemptively (see the nanoFramework thread execution docs) — a tight loop that never yields can starve other threads of CPU time, including the device's own network thread. This was observed concretely: two empty nested loops running for more than ~10 seconds would prevent the TCP thread from ever getting a chance to run, eventually causing MOGWAI NANO Studio to lose the connection even though the device itself was working correctly the whole time -
SSD1306 OLED display support — a dedicated, native (non-RPN) primitive family wrapping the
nanoFramework.Iot.Device.Ssd13xxbinding, added after measuring that dense per-pixel drawing in pure RPN (deep-cloning/re-parsing overhead per function call) was multiple orders of magnitude too slow for practical use. Fixed to 128x64 resolution over I2C Fast Mode for now — MOGWAI NANO officially supports this specific display type rather than exposing a generic OLED abstraction. Devices are managed as a single global instance (no name-based multi-display support yet), covering:ssd1306.init(bus, address),ssd1306.close,ssd1306.clear,ssd1306.printString/ssd1306.drawString(x, y, text, size, center) —printStringuses character-grid coordinates (like a text console,x=0 y=1meaning the start of the second text line), whiledrawStringuses pixel coordinates for precise, free-form placement,ssd1306.refresh,ssd1306.drawPixel,ssd1306.drawHorizontalLine/ssd1306.drawVerticalLine,ssd1306.drawRectangle(outline, hand-composed from four calls to the horizontal/vertical line primitives since the underlying binding has no dedicated rectangle-outline method),ssd1306.drawFilledRectangle, andssd1306.drawBitmap(drawing a rawMOGDatabuffer as a 1-bit-per-pixel image). A dedicatedMW.52xerror range covers display-specific failures (already open, not open, initialization failure, general operation failure) -
The device now declares
'SSD1306'as an additional skill alongside'GPIO'and'I2C', queryable the same way ('SSD1306' hasSkill) -
The new
ssd1306.*primitives are recognized and syntax-highlighted by the MOGWAI VS Code extension, following the same zero-modification stub pattern as the other NANO-specific primitives -
Lazy parsing /
frugalMode— a major memory management addition, refined over the course of extensive testing into a clean, unified mechanism. Previously, running a script parsed its entire source into a full object tree upfront, and every executed block was deep-cloned (recursively) before evaluation — necessary to protect against in-place mutation (e.g. a list built with+/AddIteminside a loop must start fresh on every iteration, or3 { (1 2 3) 4 + ? } REPEATwould print a list that keeps growing across iterations instead of the same(1 2 3 4)three times). On a device with only ~54KB of RAM, this meant a script with several function definitions could consume tens of kilobytes just to parse and clone, well before running any real logic — a script of only ~3KB of source text was measured consuming over 23KB during parsing alone.MOGCode/MOGFunctionnow support two modes, toggled withtrue/falsemogwai.frugalMode, switching takes effect on the very next execution of any block regardless of which mode it was originally parsed under:- cool (the default) — parse a block once on first use, keep the resulting object tree cached, and protect against mutation between repeated executions (loop iterations, repeated calls) via deep-cloning. Fast, but memory cost is proportional to total program size and never recovered until the block itself is discarded.
- frugal — parse a block lazily on first use, discard the parsed objects immediately after each execution, and protect against mutation by simply re-parsing from source on the next call rather than cloning — the untouched source text guarantees a fresh, uncorrupted object tree every time. Flat, stable memory footprint regardless of program size or loop iteration count, at the cost of repeating the parsing work on every single call.
Measured on a real 128x64 OLED display test (nested loops drawing 121 pixels, each via a function call): 11s in cool mode vs 23s in frugal mode, in a Release build — roughly a 2x speed trade-off for a flat memory profile. (Note: this gap nearly disappears in a Debug build, where cloning and re-parsing end up costing about the same — always benchmark this trade-off in Release.) Cool mode combined with lazy parsing (parsing only what's actually invoked, rather than upfront) already recovers most of the original memory problem for typical scripts; frugal mode remains the right choice for very long-running programs with many repeated calls on memory-constrained devices.
Updated
mogwai.info(device-side) andnano.info(Studio-side) records now also include askills:key, listing the same skills queryable viaskills/hasSkill— lets you check a device's capabilities from a single info call, without a separate query- RPN stack storage: replaced the
ArrayList-backed execution stack with a dedicatedMOGStackclass using a plainMOGObject[]array with manual growth (doubling capacity as needed, starting small to keep the per-scope memory footprint low). Since every value on the stack is already aMOGObjectreference, this removesArrayList's generic overhead entirely with no boxing trade-off — measured as a very significant speedup on stack-heavy operations (e.g. building a largeMOGDatabuffer by pushing hundreds of values withrepeatbefore converting with->data) - I2C write primitives confirmed working with large multi-byte buffers in a single transaction (not just single bytes or short sequences) — validated by initializing and clearing a full 128x64 OLED display (SSD1306) frame buffer (1024 bytes) in one
i2c.register.writecall - I2C write primitives now accept a
MOGDatabuffer by reference (&myBuffer) rather than only by value, avoiding an unnecessary copy of the buffer on every call — most useful for a large, frequently-updated buffer like a display frame buffer FOR/FORSTEPno longer allocate a newMOGNumberfor the loop counter on every iteration — the same object is now reused and its value updated in place. This changes loop variable semantics slightly: a reference to the loop variable (&i) always reflects its current value, even after the loop has moved on — code that needs to preserve a snapshot of the value from a specific iteration (e.g. collecting values into a list) must explicitly copy it (i -> 'snapshot') rather than storing a reference to the loop variable itself- The outgoing message queue is now split into a priority lane (
PROGRAM.DID.START,PROGRAM.DID.STOP,STATE.GET,PONG, and other control messages) and the regular capped lane (console.print/debug.writeoutput). The priority lane is never capped and is always drained first, so a program producing a lot of console output can no longer delay or push out a control message that MOGWAI NANO Studio is actively waiting on
Fixed
- If MOGWAI NANO Studio was...
MOGWAI NANO v0.2.0
Added
- Hexadecimal number literals (
0xFF) nano.info— remote equivalent of the device-sidemogwai.info, returning the sameMOGRecord(system version, IP, device name, platform, session, free memory, target, MOGWAI NANO version, and OEM build details) without needing anano.runround-tripnano.user.connect— guided connection shortcut combining discovery, interactive selection, and connection in one call: scans for devices, lists the ones that responded for the user to pick from, and connects to the selected one. Pushestrueon a successful connection,falseif nothing responded, no device was selected, or the connection failed — the same boolean convention asnano.connect- I2C support —
i2c.open(name, bus, address),i2c.close,i2c.write,i2c.read,i2c.register.write,i2c.register.read,i2c.scan. Devices are identified by a user-chosen name rather than repeating the bus/address pair on every call, following the same pattern as named timers.i2c.openrejects a name that's already in use with a dedicated error rather than silently overwriting it. Validated against real hardware (a DS3231 RTC module) — write, read, register auto-increment, and bus scanning all confirmed working correctly, BCD-encoded values included ->bcd/bcd->— convert a number to/from BCD (binary-coded decimal) encoding, commonly used by I2C devices like RTC modules (e.g.35 ->bcdpushes0x35;0x35 bcd->pushes35)- Skills — reusing the same mechanism as the desktop MOGWAI engine, the device now declares
'GPIO'and'I2C'as available skills, queryable withskills(returns the full list as aMOGList) andhasSkill(tests for a specific one, e.g.if ('I2C' hasSkill) then { ... }).mogwai.assertSkillis not implemented yet - Flags — named on/off state markers, reusing the same mechanism as the desktop MOGWAI engine:
flag.set/flag.clearto activate/deactivate a named flag,flag.isSet/flag.isClearto test its state (e.g.if ('MY_FLAG' flag.isSet) then { ... }). Volatile — reset on every new program run, not persisted across reboots. Anticipates the future.moglibrary system, where a flag can act as a simple guard against loading the same library twice within a single run
Updated
- Naming convention: primitives that interact directly with the console (user input or display) are now prefixed with
userfor clarity —nano.selectis renamednano.user.select, andnano.viewis renamednano.user.view. Primitives that only exchange data with the device, with no console interaction of their own, keep their existing names (nano.connect,nano.scan,nano.run, etc.) - Network protocol: replaced the JSON + Base64 message format with a lightweight delimiter-based one — fields (source, function, parameters) are now joined with a single ASCII Record Separator character (
0x1E) rather than serialized to JSON and Base64-encoded. This eliminates the allocation overhead of JSON serialization/deserialization plus Base64 encoding/decoding on every network message, which was found to cause heap fragmentation and, under sustained high-frequency traffic, an outrightOutOfMemoryException.nanoFramework.Jsonremains a device dependency — it's still used to persist local configuration (currently just the device name) to flash — but it's no longer involved in the network protocol itself. This is a breaking wire protocol change — a device and MOGWAI NANO Studio must be on matching versions; an old Studio cannot talk to a new device's firmware or vice versa.
Fixed
EvalResult.ToString()threw aNullReferenceExceptionwhenInformationswasnullon certain error paths, which could crash the device's execution dispatcher instead of reporting the original error cleanly- The outgoing message queue (
TcpServer.EnqueueMessage/SenderLoop) had no upper bound — a program producing console/debug output faster than the network could send it (e.g. a tightforeverloop withconsole.print) could grow the queue indefinitely and exhaust available memory. The queue is now capped, dropping the oldest pending message to make room for new ones once full
MogwaiNano v0.1.0
Added
Open Source Release
- MOGWAI NANO is now open source under Apache 2.0 license
- Available on GitHub at https://github.com/Sydney680928/MogwaiNano
Core Language
- Full RPN interpreter — tokenizer, stack, primitive dispatcher
- Arithmetic (
+,-,*,/), comparisons (==,!=,<,>,<=,>=), boolean operators - Stack operations (
dup,swap,drop,clear) - Control flow:
IF,IFELSE,WHILE,REPEAT,FOR,FORSTEP,FOREVER,FOREACH - Variable storage (
STO), local and global ($-prefixed) scopes - User-defined functions (
DEFUNC) with dedicated local scope, protected against name collisions with primitives and other functions - Reference sigil (
&) — direct object reference instead of copy, significantly reducing memory allocation and fragmentation on long-running scripts - Types:
MOGNumber(float-based, to leverage ESP32 hardware FPU),MOGString,MOGName,MOGList,MOGRecord,MOGKey,MOGCode,MOGFunction,MOGData(raw byte buffers,D:literal syntax) get/setprimitives for list (by index) and record (by key) access, plussizefor collection length- System primitives —
mogwai.halt,mogwai.memory(free RAM reporting),mogwai.reset,mogwai.sendMessage,mogwai.info(aMOGRecordwith system version, IP, device name, platform, session, free memory, target, MOGWAI NANO version, and OEM build details — everything in one call, useful from within an autorun program that has no active Studio connection to query) - Lifecycle hooks —
MOGWAI.onStop(any clean exit),MOGWAI.onError(unhandled error),MOGWAI.onReboot(pre-reboot cleanup, see below) - Structured error codes (
MW.xx), with a dedicatedMW.5xxrange reserved for hardware-related errors (MW.500-509GPIO,MW.510-519I2C, etc.)
Hardware Support
- GPIO —
gpio.setMode.*(input, inputPullDown, inputPullUp, output),gpio.write.high/gpio.write.low,gpio.read,gpio.toggle,gpio.close - Automatic cleanup of open GPIO pins at the end of every program run, regardless of how it ended (normal completion, error, or
STOP) - I2C, SPI, PWM and ADC packages are already referenced and validated for memory footprint — primitive implementations are planned for upcoming releases
Timers & Events
AFTER(one-shot) andEVERY(recurring) timers, named and independently startable/stoppable (timer.start,timer.stop,timer.purge)- Event subscription system (
EVENT, sugared asonEvent...doon the desktop side) — hardware events (e.g. GPIO value changes) deliver their data through an automatically-injectedeventDatalocal variable, shaped as aMOGRecord event.fire/event.purgeprimitives for manually firing or clearing registered eventsDI/EIprimitives for critical sections, protecting user code from being interrupted by pending timer/event callbacks- All pending timers and interrupt state are reset to a clean state at the start of every program run
Networking
- UDP-based device discovery (fixed port
1968) — devices respond with their name, version and platform details - Reliable TCP protocol (fixed port
9597) for remote code execution — length-prefixed, Base64-encoded JSON messages, single active client - Automatic disconnection detection via periodic
ALIVEheartbeat during long-running executions - Clean recovery on device reboot or unexpected disconnection, with no lingering blocked state on either side
Production Deployment
mogwai.rebootdevice-side primitive — called from within a running MOGWAI NANO script, it triggers the optionalMOGWAI.onReboothook for pre-reboot cleanup before actually rebootingnano.reboot/nano.haltremote commands from MOGWAI NANO Studio — force an immediate reboot/halt regardless of any program currently running on the device, bypassingMOGWAI.onRebootentirely- Persistent autorun storage — code saved to flash automatically executes on every boot, managed remotely via
nano.autorun.set/nano.autorun.get/nano.autorun.purge
Cross-Platform
- Validated on ESP32 and Raspberry Pi Pico W — the exact same compiled
.binruns unmodified on both, despite very different underlying architectures (Xtensa LX6 vs Cortex-M0+)
MOGWAI NANO Studio
- Desktop companion application built on the desktop MOGWAI engine
- Integrated Terminal.Gui-based code editor with F5-to-run workflow
- Extended primitives, exposed as regular MOGWAI host functions:
nano.connect,nano.disconnect,nano.isConnected— connection management.nano.connectpushestrue/falsedepending on success, a deliberate exception to the pattern below: connecting is expected to sometimes fail, so a boolean fits a straightforward feasibility checknano.name,nano.name.set— read or set the connected device's name, persisted on the device and reported as thenamefield innano.scan/nano.selectresults. Defaults to"MogwaiNanoDevice"; useful to tell multiple devices apart on the same networknano.scan— UDP network discovery (fixed 1s duration, with retransmission every 250ms to compensate for broadcast packet loss), returns a list of records (name, version, session, IP, platform, target, OEM, firmware version), deduplicated by IP. Thesessionfield is a random number generated once at boot, letting you detect a silent device reboot between two scans even without any visible errornano.select— runs its own scan and displays the responding devices (platform, IP) for interactive console selection; pushes the selected device's scan record on the stack, ornullif aborted or nothing respondednano.run— desugars and sends a code block for remote execution; unlikenano.connect, failure raises a distinctMW.xxerror (device not connected, unreachable, or busy already running something) rather than returning a boolean — running is expected to normally succeed, so a failure is treated as an incident with a diagnosable cause, not a routine outcomenano.state,nano.isRunning,nano.memory— query the connected device's current execution state and free RAM (GC.Run(false)result, non-blocking)nano.autorun.set,nano.autorun.get,nano.autorun.purge— manage code stored on the device for automatic execution on every bootnano.halt,nano.reboot— force an immediate halt/reboot on the device, bypassing theMOGWAI.onReboothook.nano.haltstops whatever is currently running (whether started vianano.runor as a stored autorun program) and returns the device's state fromRUNNINGtoIDLE, ready for a newnano.runnano.view— attaches to the currently running program on the device and displays its live console output (?/console.print,debug.write) in real time; exit withCtrl+C. Withoutnano.viewactive, output from anano.runor an autorun program is not displayed at all —nano.runitself only waits for confirmation that the program has started, it doesn't wait for it to finish or show anything. Likenano.run, failure raises anMW.xxerror rather than returning a boolean
- Zero-modification compatibility with the existing MOGWAI VS Code extension — canonical NANO primitives are declared as no-op stubs on the desktop engine purely so the extension can recognize and highlight them; using them outside of a
nano.runcontext on the desktop engine simply raises an "unknown word" error, with no other consequence
Known Limitations
- No step-by-step debugging on the device runtime (yet)
- Network configuration deployment (
nanoff --networkdeployment) support on Raspberry Pi Pico W is still being confirmed with the nanoFramework team MogwaiNanoRuntime.WaitResponsecorrelates a response to a request byFunctionname only, not by a unique request identifier — if two requests of the sameFunctionwere ever in flight concurrently, the wrong response could be matched to the wrong caller. Not an issue with the current sequential REPL-driven usage, but worth revisiting if concurrentnano.*calls are ever introduced.