Modulino Buttons + UNO Q - Interactive panel
Basic example on how to use the Modulino Buttons together with the Arduino UNO Q
Devices & Components
1
Arduino® UNO™ Q 2GB
1
Modulino™ Buttons
Software & Tools
Arduino App Lab
Project description
Code
main
python
Python main file
1# Modulino Buttons — Interactive Panel 2# 3# Runs on the UNO Q Linux side (MPU). 4# Receives button events pushed by the sketch via Bridge.notify() and 5# broadcasts real-time state updates to all connected browsers over Socket.IO. 6# The browser sends "toggle_led" to control each button's onboard LED. 7 8import os 9 10from arduino.app_utils import App, Bridge 11from arduino.app_bricks.web_ui import WebUI 12 13# ── Web UI setup ────────────────────────────────────────────────────────────── 14_ui_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "ui") 15ui = WebUI(assets_dir_path=_ui_dir) 16 17# ── Shared state ────────────────────────────────────────────────────────────── 18# counters : per-event-type press counts for each button (A=0, B=1, C=2) 19# leds : current on/off state of each onboard LED 20state = { 21 "counters": { 22 "press": [0, 0, 0], 23 "release": [0, 0, 0], 24 "double_tap": [0, 0, 0], 25 }, 26 "leds": [False, False, False], 27} 28 29 30def _broadcast(room=None): 31 """Push the current state to browsers. room=sid targets a single client.""" 32 ui.send_message("state_update", state, room=room) 33 34 35# ── MCU → Python: receive button events from the sketch ────────────────────── 36def on_button_event(index: int, event_type: str): 37 """Called by the sketch on press, release, or double-tap.""" 38 if 0 <= index <= 2 and event_type in state["counters"]: 39 state["counters"][event_type][index] += 1 40 _broadcast() 41 42 43Bridge.provide("button_event", on_button_event) 44 45 46# ── Connection handler ──────────────────────────────────────────────────────── 47def on_connect(sid): 48 """Send current state immediately when a browser connects.""" 49 _broadcast(room=sid) 50 51 52# ── Browser → Python: toggle one LED ───────────────────────────────────────── 53def on_toggle_led(sid, data): 54 """Flip the LED at the given index and apply it to the hardware.""" 55 idx = int(data.get("index", -1)) 56 if 0 <= idx <= 2: 57 state["leds"][idx] = not state["leds"][idx] 58 Bridge.call("set_led", idx, 1 if state["leds"][idx] else 0) 59 _broadcast() 60 61 62# ── Register handlers ───────────────────────────────────────────────────────── 63ui.on_connect(on_connect) 64ui.on_message("toggle_led", on_toggle_led) 65 66App.run()
index
markup
HTML file
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>Modulino Buttons — Interactive Panel</title> 7 <link rel="stylesheet" href="styles.css" /> 8</head> 9<body> 10 11 <header> 12 <h1>🔘 Modulino Buttons — Interactive Panel</h1> 13 <span id="status" class="status connecting">Connecting…</span> 14 </header> 15 16 <main> 17 18 <section class="card"> 19 <h2>Button Events</h2> 20 <div class="buttons-row"> 21 22 <div class="btn-card" id="card-0"> 23 <div class="btn-circle"></div> 24 <div class="btn-label">Button A</div> 25 <div class="event-rows"> 26 <div class="event-row press"> 27 <span class="event-name">Press</span> 28 <span class="event-count" id="press-0">0</span> 29 </div> 30 <div class="event-row release"> 31 <span class="event-name">Release</span> 32 <span class="event-count" id="release-0">0</span> 33 </div> 34 <div class="event-row dtap"> 35 <span class="event-name">Double tap</span> 36 <span class="event-count" id="dtap-0">0</span> 37 </div> 38 </div> 39 </div> 40 41 <div class="btn-card" id="card-1"> 42 <div class="btn-circle"></div> 43 <div class="btn-label">Button B</div> 44 <div class="event-rows"> 45 <div class="event-row press"> 46 <span class="event-name">Press</span> 47 <span class="event-count" id="press-1">0</span> 48 </div> 49 <div class="event-row release"> 50 <span class="event-name">Release</span> 51 <span class="event-count" id="release-1">0</span> 52 </div> 53 <div class="event-row dtap"> 54 <span class="event-name">Double tap</span> 55 <span class="event-count" id="dtap-1">0</span> 56 </div> 57 </div> 58 </div> 59 60 <div class="btn-card" id="card-2"> 61 <div class="btn-circle"></div> 62 <div class="btn-label">Button C</div> 63 <div class="event-rows"> 64 <div class="event-row press"> 65 <span class="event-name">Press</span> 66 <span class="event-count" id="press-2">0</span> 67 </div> 68 <div class="event-row release"> 69 <span class="event-name">Release</span> 70 <span class="event-count" id="release-2">0</span> 71 </div> 72 <div class="event-row dtap"> 73 <span class="event-name">Double tap</span> 74 <span class="event-count" id="dtap-2">0</span> 75 </div> 76 </div> 77 </div> 78 79 </div> 80 </section> 81 82 <section class="card"> 83 <h2>Onboard LED Control</h2> 84 <div class="leds-row"> 85 <div class="led-card"> 86 <div class="led-circle" id="led-0"></div> 87 <div class="led-label">LED A</div> 88 <button class="btn led-toggle-btn" data-index="0">Toggle</button> 89 </div> 90 <div class="led-card"> 91 <div class="led-circle" id="led-1"></div> 92 <div class="led-label">LED B</div> 93 <button class="btn led-toggle-btn" data-index="1">Toggle</button> 94 </div> 95 <div class="led-card"> 96 <div class="led-circle" id="led-2"></div> 97 <div class="led-label">LED C</div> 98 <button class="btn led-toggle-btn" data-index="2">Toggle</button> 99 </div> 100 </div> 101 </section> 102 103 </main> 104 105 <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script> 106 <script src="app.js"></script> 107</body> 108</html>
app
js
Javascript file
1const socket = io(); 2 3const statusEl = document.getElementById("status"); 4 5// Track previous counter values to detect changes for the flash effect 6const prev = { 7 press: [0, 0, 0], 8 release: [0, 0, 0], 9 double_tap: [0, 0, 0], 10}; 11 12// Maps state key → DOM id prefix 13const EVENT_IDS = { press: "press", release: "release", double_tap: "dtap" }; 14 15function flash(el) { 16 el.classList.remove("flash"); 17 void el.offsetWidth; // force reflow so re-adding the class retriggers the transition 18 el.classList.add("flash"); 19 setTimeout(() => el.classList.remove("flash"), 400); 20} 21 22// Single source of truth: only update the DOM here, in response to state_update 23function applyState(state) { 24 const { counters, leds } = state; 25 26 for (let i = 0; i < 3; i++) { 27 for (const [key, prefix] of Object.entries(EVENT_IDS)) { 28 const val = counters[key][i]; 29 const el = document.getElementById(`${prefix}-${i}`); 30 if (val !== prev[key][i]) { 31 el.textContent = val; 32 flash(el); 33 prev[key][i] = val; 34 } 35 } 36 37 const pressed = counters.press[i] > counters.release[i]; 38 document.getElementById(`card-${i}`).classList.toggle("pressed", pressed); 39 document.getElementById(`led-${i}`).classList.toggle("on", leds[i]); 40 } 41} 42 43// Emit toggle_led and wait for the server's state_update — no optimistic update 44document.querySelectorAll(".led-toggle-btn").forEach(btn => { 45 btn.addEventListener("click", () => { 46 socket.emit("toggle_led", { index: parseInt(btn.dataset.index, 10) }); 47 }); 48}); 49 50socket.on("connect", () => { statusEl.className = "status connected"; statusEl.textContent = "● Connected"; }); 51socket.on("disconnect", () => { statusEl.className = "status disconnected"; statusEl.textContent = "● Disconnected"; }); 52socket.on("connect_error", () => { statusEl.className = "status connecting"; statusEl.textContent = "Connecting…"; }); 53 54socket.on("state_update", data => { if (data) applyState(data); });
sketch
cpp
Arduino sketch
1/* 2 * Modulino Buttons — Interactive Panel 3 * 4 * Uses the Button2 library to detect press, release, and double-tap events 5 * on all 3 Modulino Buttons (A, B, C). Events are pushed to Python via 6 * Bridge.notify(). Each button's onboard orange LED can be toggled 7 * independently from the browser using the "set_led" Bridge function. 8 * 9 * Hardware: Arduino UNO Q + Modulino Buttons (Qwiic) 10 */ 11 12#include <Arduino_RouterBridge.h> 13#include <Arduino_Modulino.h> 14#include <Button2.h> 15 16// ── Modulino + Button2 objects ─────────────────────────────────── 17ModulinoButtons modulinoButtons; 18Button2 btn[3]; 19 20// ── LED state (index 0 = A, 1 = B, 2 = C) ─────────────────────── 21bool ledState[3] = {false, false, false}; 22 23// ── Helper: apply current ledState[] to the hardware ──────────── 24void applyLeds() { 25 modulinoButtons.setLeds(ledState[0], ledState[1], ledState[2]); 26} 27 28// ── Bridge callback: set one LED from Python ───────────────────── 29// Python sends: set_led(index, state) 30// index : 0 (A), 1 (B), or 2 (C) 31// state : 1 = on, 0 = off 32String rpc_set_led(int index, int newState) { 33 if (index < 0 || index > 2) return "{\"ok\":false}"; 34 ledState[index] = (newState != 0); 35 applyLeds(); 36 return "{\"ok\":true}"; 37} 38 39// ── Button2 state handlers (one per button) ────────────────────── 40// Button2 polls these to read button state. 41// Returns LOW when pressed, HIGH when released (pull-up convention). 42uint8_t btn0State() { modulinoButtons.update(); return modulinoButtons.isPressed(0) ? LOW : HIGH; } 43uint8_t btn1State() { modulinoButtons.update(); return modulinoButtons.isPressed(1) ? LOW : HIGH; } 44uint8_t btn2State() { modulinoButtons.update(); return modulinoButtons.isPressed(2) ? LOW : HIGH; } 45 46// ── Helper: resolve which button index triggered the event ─────── 47int btnIndex(Button2& b) { 48 for (int i = 0; i < 3; i++) if (&b == &btn[i]) return i; 49 return -1; 50} 51 52// ── Button2 event handlers ─────────────────────────────────────── 53void onPress(Button2& b) { 54 int i = btnIndex(b); 55 if (i >= 0) Bridge.notify("button_event", i, "press"); 56} 57 58void onRelease(Button2& b) { 59 int i = btnIndex(b); 60 if (i >= 0) Bridge.notify("button_event", i, "release"); 61} 62 63void onDoubleClick(Button2& b) { 64 int i = btnIndex(b); 65 if (i >= 0) Bridge.notify("button_event", i, "double_tap"); 66} 67 68void setup() { 69 Bridge.begin(); // connect to Python first 70 Modulino.begin(); 71 modulinoButtons.begin(); 72 73 // Make sure all LEDs start off 74 applyLeds(); 75 76 // Configure Button2 for each of the 3 buttons 77 typedef uint8_t (*StateFunc)(); 78 StateFunc stateFuncs[3] = {btn0State, btn1State, btn2State}; 79 80 for (int i = 0; i < 3; i++) { 81 btn[i].setDebounceTime(35); 82 btn[i].setButtonStateFunction(stateFuncs[i]); 83 btn[i].setPressedHandler(onPress); 84 btn[i].setReleasedHandler(onRelease); 85 btn[i].setDoubleClickHandler(onDoubleClick); 86 btn[i].begin(BTN_VIRTUAL_PIN); 87 } 88 89 // Expose "set_led" so Python can call it from the Web UI 90 Bridge.provide("set_led", rpc_set_led); 91} 92 93void loop() { 94 // Let Button2 process all button state machines 95 for (int i = 0; i < 3; i++) { 96 btn[i].loop(); 97 } 98}
styles
css
Style file
1/* ── Design tokens ───────────────────────────────────────────── */ 2:root { 3 --bg: #0f1117; 4 --surface: #1c1f2a; 5 --border: #2e3244; 6 --accent: #4fa3e0; 7 --text: #e2e8f0; 8 --muted: #8892a4; 9 --green: #22c55e; 10 --red: #ef4444; 11 --radius: 10px; 12 13 /* App-specific */ 14 --btn-pressed: #3a7bd5; 15 --led-orange: #ff8c00; 16 --led-ring: #ffb347; 17} 18 19/* ── Base reset ──────────────────────────────────────────────── */ 20* { box-sizing: border-box; margin: 0; padding: 0; } 21 22body { 23 font-family: 'Segoe UI', system-ui, sans-serif; 24 background: var(--bg); 25 color: var(--text); 26 min-height: 100vh; 27 padding: 1rem; 28} 29 30/* ── Header + status badge ───────────────────────────────────── */ 31header { 32 display: flex; 33 flex-direction: column; 34 align-items: center; 35 gap: 0.5rem; 36 margin-bottom: 1.5rem; 37 text-align: center; 38} 39 40header h1 { font-size: 1.35rem; letter-spacing: -0.02em; } 41 42.status { 43 font-size: 0.8rem; 44 font-weight: 600; 45 padding: 0.25rem 0.75rem; 46 border-radius: 999px; 47 letter-spacing: 0.04em; 48 text-transform: uppercase; 49} 50 51.status.connected { background: var(--green); color: #fff; } 52.status.disconnected { background: var(--red); color: #fff; } 53.status.connecting { background: var(--muted); color: #fff; } 54 55/* ── Cards ───────────────────────────────────────────────────── */ 56main { display: flex; flex-direction: column; gap: 1.25rem; } 57 58.card { 59 background: var(--surface); 60 border: 1px solid var(--border); 61 border-radius: var(--radius); 62 padding: 1.25rem 1.5rem; 63} 64 65.card h2 { 66 font-size: 0.95rem; 67 font-weight: 600; 68 color: var(--muted); 69 text-transform: uppercase; 70 letter-spacing: 0.06em; 71 margin-bottom: 1rem; 72} 73 74/* ── Button event cards ──────────────────────────────────────── */ 75.buttons-row { 76 display: flex; 77 gap: 1rem; 78 justify-content: center; 79 flex-wrap: wrap; 80} 81 82.btn-card { 83 display: flex; 84 flex-direction: column; 85 align-items: center; 86 gap: 0.6rem; 87 background: var(--bg); 88 border: 2px solid var(--border); 89 border-radius: var(--radius); 90 padding: 1rem 1.25rem; 91 min-width: 110px; 92 transition: border-color 0.15s; 93} 94 95.btn-card.pressed { border-color: var(--btn-pressed); } 96 97.btn-circle { 98 width: 40px; 99 height: 40px; 100 border-radius: 50%; 101 background: var(--surface); 102 border: 3px solid var(--border); 103 transition: background 0.1s, border-color 0.1s; 104} 105 106.btn-card.pressed .btn-circle { 107 background: var(--btn-pressed); 108 border-color: var(--accent); 109} 110 111.btn-label { font-size: 0.85rem; color: var(--muted); } 112 113/* ── Event counter rows ──────────────────────────────────────── */ 114.event-rows { 115 width: 100%; 116 display: flex; 117 flex-direction: column; 118 gap: 4px; 119} 120 121.event-row { 122 display: flex; 123 justify-content: space-between; 124 align-items: center; 125 gap: 0.5rem; 126 font-size: 0.78rem; 127} 128 129.event-name { color: var(--muted); } 130.event-count { font-weight: 700; color: var(--text); min-width: 24px; text-align: right; } 131 132.event-row.press .event-name { color: var(--accent); } 133.event-row.release .event-name { color: var(--green); } 134.event-row.dtap .event-name { color: #f5a623; } 135 136.event-count.flash { color: #ffe080; } 137 138/* ── LED controls ────────────────────────────────────────────── */ 139.leds-row { 140 display: flex; 141 gap: 1rem; 142 justify-content: center; 143 flex-wrap: wrap; 144} 145 146.led-card { 147 display: flex; 148 flex-direction: column; 149 align-items: center; 150 gap: 0.6rem; 151 background: var(--bg); 152 border: 1px solid var(--border); 153 border-radius: var(--radius); 154 padding: 1rem 1.5rem; 155 min-width: 90px; 156} 157 158.led-circle { 159 width: 40px; 160 height: 40px; 161 border-radius: 50%; 162 background: var(--surface); 163 border: 3px solid var(--border); 164 transition: background 0.15s, border-color 0.15s, box-shadow 0.15s; 165} 166 167.led-circle.on { 168 background: var(--led-orange); 169 border-color: var(--led-ring); 170 box-shadow: 0 0 14px #ff8c0066; 171} 172 173.led-label { font-size: 0.85rem; color: var(--muted); } 174 175/* ── Buttons ─────────────────────────────────────────────────── */ 176.btn { 177 background: var(--accent); 178 color: #fff; 179 border: none; 180 border-radius: 8px; 181 padding: 0.5rem 1rem; 182 font-size: 0.85rem; 183 font-weight: 600; 184 cursor: pointer; 185 transition: opacity 0.15s; 186} 187 188.btn:hover { opacity: 0.85; } 189.btn:active { opacity: 0.7; } 190 191/* ── Responsive ──────────────────────────────────────────────── */ 192@media (min-width: 600px) { 193 main { max-width: 700px; margin: 0 auto; } 194}
Arduino App Lab
Modulino Buttons - Interactive Panel
Physical button presses shown live in the browser. Control each onboard LED independently from the UI.
🔘
Modulino Buttons - Interactive Panel
Comments
Only logged in users can leave comments