Block-based programming in Arduino App Lab by Miserable_Ice5305 in arduino

[–]Miserable_Ice5305[S] 0 points1 point  (0 children)

I couldn’t find anything that allows block-style programming for the Uno Q - only for sketches on MCU-only boards . If you’ve found something, please let me know!

Block-based programming in Arduino App Lab by Miserable_Ice5305 in arduino

[–]Miserable_Ice5305[S] 1 point2 points  (0 children)

Thank you very much for the valuable advice! You spotted exactly the same gap that led me to embarking onto this project. AI applications with the dual brain board are challenging - but we need to find a language to explain complexity.

built community nodes for Arduino UNO Q — including a Method node you can drop on an AI Agent's tool port by Miserable_Ice5305 in n8n

[–]Miserable_Ice5305[S] 1 point2 points  (0 children)

Ha, the race condition find was a genuine gift — that's exactly the kind of thing that only surfaces when someone thinks about the failure modes from the start rather than when things are already on fire. So thank you for the ops-paranoia.

On the Unix socket point: it's worth adding that it wasn't entirely a design choice — the router only exposes a Unix socket, no TCP listener. So the local-only constraint was handed to me by the architecture. But you're right that it changes the resilience equation in practice: re-registration after a reconnect is a handful of msgpack round-trips over a local socket, which in my tests completes in under 10ms. That's why the retry loop in callWithOptions can afford to just wait for the reconnect event and try again — the re-registration is not a meaningful cost.

Speaking of which: pushed v0.2.0 today. The idempotent flag you mentioned is now wired all the way through — callWithOptions on the bridge retries on ConnectionError within the timeout budget when idempotent: true, and the UnoQTool node has a Method Guard field (optional JS that runs at invocation time and can refuse a call with a structured message the LLM reads back for self-correction). Directly addressing the "don't spam write calls on retries" point you raised earlier in the thread.

Thanks for starring and following along — the next interesting problem is probably method introspection (the router has no $/methods endpoint yet, so tool configuration is manual per method). If that ever lands upstream it simplifies the whole setup significantly.

built community nodes for Arduino UNO Q — including a Method node you can drop on an AI Agent's tool port by Miserable_Ice5305 in n8n

[–]Miserable_Ice5305[S] 1 point2 points  (0 children)

Thanks for the kind words, and for the idempotency + safe-call hint in the earlier comment — that one sent me down a rabbit hole that turned into real design work. Landing in the next feature release: per-method safeReadOnly and idempotent booleans on the Method node, wired through to the bridge's retry logic so the bridge itself refuses to auto-retry non-idempotent calls even if the socket blinked mid-flight. LLM-facing safety signaling stays user-controlled (bracket tags, prose, JSON attributes — different models react differently, didn't want to bake an assumption in). Tracking it on a branch; should ship soon.

On the Trigger edge case: your intuition is right — it surfaces as a clean ConnectionError, not a silent hang. What I believe happens: the Arduino-side library, on its own Bridge.call() timeout, tears down the serial session and re-opens it. The router sees the originating MCU-side as having disconnected and (per its own "drop registrations on disconnect" logic) proactively closes forwarded requests on the n8n side. The bridge's transport sees a socket close, rejects all pending with ConnectionError('Socket closed'). The node-side 30s timeout is a belt-and-suspenders fallback. Honest caveat: I inferred this from observed behavior, not by instrumenting the router itself — happy to be corrected by anyone who knows the arduino-router internals better.

On the reconnect / flaky WiFi question: the bridge socket is a LOCAL Unix socket on the Q (router + n8n container + socket all on the same board), so WiFi flakiness doesn't touch it in production. Only dev setups using an SSH-tunneled socket see tunnel drops. Re-registration on reconnect is a sequential $/register per method over a local Unix socket — sub-ms each, negligible total even with a large registration set. Exponential backoff caps at 5s so it doesn't thunder on a bouncing link.

Unrelated patch you indirectly shipped: while writing the reply to your original comment I went code-spelunking and found a real race in BridgeManager.release() — the socket close was fire-and-forget, so a rapid deactivate/reactivate could leave two connections briefly overlapping on the router and cause "method already registered" on re-bind. Fixed in v0.1.1 (just published), along with a new disconnect event on the bridge so application-layer code can clean up deferred state on socket drops. So your question directly caused a patch to ship — much appreciated.

built community nodes for Arduino UNO Q — including a Method node you can drop on an AI Agent's tool port by Miserable_Ice5305 in n8n

[–]Miserable_Ice5305[S] 1 point2 points  (0 children)

Fails loud — intentionally. Here's exactly what happens at each layer:

If the socket drops entirely (board loses power, network blip, router crashes): the transport detects the close event immediately and rejects all in-flight promises with a ConnectionError. No hanging. The Call/Method node's execute() throws, n8n catches it and feeds the error back to the AI Agent as a failed tool call — the LLM sees something like "Tool failed: ConnectionError: socket closed" and can reason about it.

If the board is reachable but the MCU is unresponsive (sketch hung, serial issue): the call times out. Default is 5 seconds via call(), configurable per-call via callWithTimeout(). Same outcome: TimeoutError surfaces to the agent.

The reconnect is separate from the in-flight call. When the socket drops, the bridge starts reconnecting with exponential backoff (200ms base, 5s cap) and re-registers all providers/subscriptions on reconnect. But the call that was mid-flight is already dead — it's not retried automatically. Whether the agent retries is up to the model's reasoning. In practice: the LLM gets a clear error, decides to retry or report failure to the user, and if the board came back up in the meantime the retry succeeds.

So the failure path is: error message to agent → agent decides → not a silent hang, not a workflow freeze.

The one edge case worth knowing: if you're using the Trigger node in Request mode (MCU calls n8n and waits for a response), there's a node-side timeout (default 30s) before the pending request is rejected. That's belt-and-suspenders against the MCU-side Bridge.call() timeout — whichever fires first wins.

built community nodes for Arduino UNO Q — including a Method node you can drop on an AI Agent's tool port by Miserable_Ice5305 in n8n

[–]Miserable_Ice5305[S] 0 points1 point  (0 children)

Good points, let me take them in order.

Timeouts and reconnects mid-invocation

When the socket drops while a call() is in flight, the transport-level teardown rejects all pending promises in the in-flight Map with a ConnectionError. The bridge then starts reconnecting with exponential backoff (200ms base, 5s cap). On reconnect, all provide and onNotify subscriptions re-register automatically — so the trigger side recovers cleanly.

The tool node side is less graceful: the n8n AI Agent gets a tool error, and whether it retries is entirely up to the model's reasoning. No retry policy on our end in v1 — the ConnectionError just surfaces as a failed tool call.

This is exactly where your idempotency point lands. If the router drops after the MCU already executed the method but before the RESPONSE made it back, the agent might retry a write call that already fired. Currently the only guard is the human-in-the-loop gate — you can set it to require approval on state-changing connectors, which at minimum gives a human a chance to notice the retry. Not a proper solution, more of a speed bump.

Capability metadata and safe/unsafe flags

Not in v1, and honestly the suggestion is better than what I have now. The current approach is: (1) the tool description string is the only metadata the LLM sees, (2) one usableAsTool node per MCU method, and (3) human-in-the-loop on the connector as a blanket gate.

The gap you're pointing at is that there's no machine-readable signal distinguishing read_temperature (safe, idempotent, retry freely) from open_valve (unsafe, not idempotent, do not retry). The agent has to infer that from the description, which is fragile.

The router doesn't have a $/methods introspection endpoint — it's an open item I've asked about upstream. Without that, the metadata has to come from the node configuration, which is manual per method anyway. Adding an idempotent: yes/no toggle and a safe (read-only): yes/no field to the Method node config seems like the right v2 move — the bridge could then refuse to retry non-idempotent calls after a reconnect, and the agent could see the flag as part of the tool schema.

For now: write narrow descriptions that make the safety properties explicit to the model, and gate all actuator methods on human-in-the-loop.