UNO Q Modulino address changer
App Lab project to change the Modulino Nodes I2C address
Devices & Components
1
Modulino™ Pixels
1
Modulino™ Knob
1
Modulino™ Buttons
1
Modulino™ Latch Relay
1
Modulino™ LED Matrix
1
Modulino™ Joystick
1
Arduino® UNO™ Q 4GB
1
Modulino™ Buzzer
1
Modulino™ Vibro
Software & Tools
Arduino App Lab
Project description
Code
main.py
python
Python code
1""" 2Modulino Address Changer — Python host 3Bridges the Arduino sketch to a Web UI over REST. 4 5Endpoints: 6 GET /api/devices — list all discovered devices 7 POST /api/change_address — body: {cur_addr, new_addr} 8 POST /api/rescan — re-scan the I²C bus 9""" 10 11import threading 12from arduino.app_utils import App, Bridge 13from arduino.app_bricks.web_ui import WebUI 14 15ui = WebUI() 16 17# ── Device registry ─────────────────────────────────────────────────────────── 18_lock = threading.Lock() 19devices = {} # addr(int) -> device dict 20 21def _make_device(addr, name, pinstrap, default_addr, configurable): 22 configurable = bool(configurable) 23 modified = configurable and pinstrap >= 0 and (addr != default_addr) 24 return { 25 "addr": addr, 26 "addrHex": f"0x{addr:02X}", 27 "name": name, 28 "pinstrap": pinstrap, 29 "pinstrapHex": f"0x{pinstrap:02X}" if pinstrap >= 0 else "-", 30 "defaultAddr": default_addr, 31 "defaultAddrHex": f"0x{default_addr:02X}", 32 "configurable": configurable, 33 "modified": modified, 34 } 35 36# ── Bridge event handlers (Arduino → Python) ────────────────────────────────── 37_scan_done = threading.Event() 38_change_done = threading.Event() 39_last_result: dict = {} 40 41def on_scan_start(): 42 with _lock: 43 devices.clear() 44 _scan_done.clear() 45 46def on_device_found(addr: int, name: str, pinstrap: int, 47 default_addr: int, configurable: int): 48 with _lock: 49 devices[addr] = _make_device(addr, name, pinstrap, default_addr, configurable) 50 51def on_scan_complete(): 52 _scan_done.set() 53 54def on_change_result(success: int, cur_addr: int, new_addr: int, message: str): 55 global _last_result 56 _last_result = { 57 "success": bool(success), 58 "cur_addr": cur_addr, 59 "new_addr": new_addr, 60 "message": message, 61 } 62 _change_done.set() 63 64Bridge.provide("scan_start", on_scan_start) 65Bridge.provide("device_found", on_device_found) 66Bridge.provide("scan_complete", on_scan_complete) 67Bridge.provide("change_result", on_change_result) 68 69# ── REST API ────────────────────────────────────────────────────────────────── 70def get_devices(): 71 with _lock: 72 return {"ok": True, "devices": list(devices.values())} 73 74def post_change_address(body: dict): 75 cur = body.get("cur_addr") 76 new = body.get("new_addr") 77 if cur is None or new is None: 78 return {"ok": False, "error": "cur_addr and new_addr are required"} 79 80 _change_done.clear() 81 # Clear devices now; on_scan_start (triggered by re-scan inside change_address) 82 # will also clear them, but doing it here prevents stale reads. 83 with _lock: 84 devices.clear() 85 86 Bridge.notify("change_address", int(cur), int(new)) 87 88 if not _change_done.wait(timeout=5.0): 89 return {"ok": False, "error": "Timeout: no response from Arduino"} 90 91 result = dict(_last_result) 92 93 # change_address on the Arduino always re-scans after the change. 94 # Wait for that scan to finish so we can return an up-to-date device list. 95 _scan_done.wait(timeout=10.0) 96 97 with _lock: 98 result["devices"] = list(devices.values()) 99 100 result["ok"] = result.pop("success") 101 return result 102 103def post_rescan(body: dict = None): 104 _scan_done.clear() 105 Bridge.notify("rescan") 106 _scan_done.wait(timeout=10.0) 107 with _lock: 108 return {"ok": True, "devices": list(devices.values())} 109 110ui.expose_api("GET", "/api/devices", get_devices) 111ui.expose_api("POST", "/api/change_address", post_change_address) 112ui.expose_api("POST", "/api/rescan", post_rescan) 113 114App.run()
sketch.ino
cpp
Arduino code
1/* 2 * Modulino — Address Changer (Web UI edition) 3 * 4 * Scans the I²C bus, reports discovered Modulino devices to Python via 5 * Bridge, and handles address-change commands from the Web UI. 6 * 7 * Copyright (c) 2025 Arduino 8 * SPDX-License-Identifier: MPL-2.0 9 */ 10 11#include <Arduino_RouterBridge.h> 12#include "Wire.h" 13 14// ── Fixed-address devices (no MCU — address cannot be changed) ─────────────── 15static const uint8_t FIXED_ADDRS[] = { 0x29, 0x44, 0x53, 0x6A, 0x6B }; 16static const int NUM_FIXED = (int)(sizeof(FIXED_ADDRS) / sizeof(FIXED_ADDRS[0])); 17 18static bool isFixedAddr(uint8_t addr) { 19 for (int i = 0; i < NUM_FIXED; i++) 20 if (FIXED_ADDRS[i] == addr) return true; 21 return false; 22} 23 24static String fixedAddrToName(uint8_t addr) { 25 switch (addr) { 26 case 0x29: return "Distance"; 27 case 0x44: return "Thermo"; 28 case 0x53: return "Light"; 29 case 0x6A: 30 case 0x6B: return "Movement"; 31 default: return "Unknown"; 32 } 33} 34 35// Pinstrap is the byte read from register 0x00 of a configurable device. 36// Default I²C address = pinstrap / 2. 37static String pinstrapToName(uint8_t pinstrap) { 38 switch (pinstrap) { 39 case 0x04: return "Latch Relay"; 40 case 0x28: return "Opto Relay"; 41 case 0x3C: return "Buzzer"; 42 case 0x58: return "Joystick"; 43 case 0x6C: return "Pixels"; 44 case 0x70: return "Vibro"; 45 case 0x74: return "Knob"; 46 case 0x76: return "Knob"; 47 case 0x72: return "LED Matrix"; 48 case 0x7C: return "Buttons"; 49 default: return "Unknown"; 50 } 51} 52 53// ── I²C scan ───────────────────────────────────────────────────────────────── 54static void discoverDevices() { 55 Bridge.notify("scan_start"); 56 57 for (int addr = 8; addr <= 0x77; addr++) { 58 Wire1.beginTransmission(addr); 59 if (Wire1.endTransmission() != 0) continue; 60 61 if (isFixedAddr((uint8_t)addr)) { 62 // Fixed device: pinstrap = -1, defaultAddr = addr, configurable = 0 63 Bridge.notify("device_found", addr, fixedAddrToName((uint8_t)addr), -1, addr, 0); 64 continue; 65 } 66 67 // Configurable device: read pinstrap from register 0x00 68 Wire1.beginTransmission(addr); 69 Wire1.write(0x00); 70 Wire1.endTransmission(); 71 delay(50); 72 73 Wire1.requestFrom(addr, 1); 74 if (Wire1.available()) { 75 uint8_t pinstrap = Wire1.read(); 76 while (Wire1.available()) Wire1.read(); // drain 77 int defAddr = (int)(pinstrap / 2); 78 Bridge.notify("device_found", addr, pinstrapToName(pinstrap), (int)pinstrap, defAddr, 1); 79 } else { 80 if (addr < 0x78) { 81 Bridge.notify("device_found", addr, String("Unknown"), 0, addr, 1); 82 } 83 } 84 } 85 86 Bridge.notify("scan_complete"); 87} 88 89// ── RPCs (called by Python via Bridge) ─────────────────────────────────────── 90 91// Change the I²C address of the device at curAddr to newAddr. 92// curAddr = 0 → broadcast to all devices 93// newAddr = 0 → reset to default pinstrap address 94bool change_address(int curAddr, int newAddr) { 95 // Validate new address (0 is special: means "reset to default") 96 if (newAddr != 0 && (newAddr < 8 || newAddr > 0x77)) { 97 Bridge.notify("change_result", 0, curAddr, newAddr, 98 String("Invalid address — must be 0x08–0x77 or 0 (reset to default)")); 99 return false; 100 } 101 102 // Fixed-address devices cannot be reconfigured 103 if (curAddr != 0 && isFixedAddr((uint8_t)curAddr)) { 104 Bridge.notify("change_result", 0, curAddr, newAddr, 105 String("This device has a fixed address and cannot be changed")); 106 return false; 107 } 108 109 // Send the 'C','F',newAddr*2 command packet 110 uint8_t data[48] = { 'C', 'F', (uint8_t)(newAddr * 2) }; 111 memset(data + 3, 0, sizeof(data) - 3); 112 113 Wire1.beginTransmission((uint8_t)curAddr); 114 Wire1.write(data, 40); 115 Wire1.endTransmission(); 116 117 delay(500); 118 119 bool ok; 120 if (newAddr == 0 || curAddr == 0) { 121 // Cannot verify: target address is unknown (default) or broadcast was used 122 ok = true; 123 } else { 124 Wire1.requestFrom(newAddr, 1); 125 ok = Wire1.available() > 0; 126 while (Wire1.available()) Wire1.read(); 127 } 128 129 Bridge.notify("change_result", ok ? 1 : 0, curAddr, newAddr, 130 ok ? String("OK") : String("No response at new address — change may have failed")); 131 132 // Always re-scan so Python gets the updated device list 133 discoverDevices(); 134 return ok; 135} 136 137bool rescan() { 138 discoverDevices(); 139 return true; 140} 141 142// ── Setup / Loop ───────────────────────────────────────────────────────────── 143void setup() { 144 Wire1.begin(); 145 Bridge.begin(); 146 Bridge.provide_safe("change_address", change_address); 147 Bridge.provide_safe("rescan", rescan); 148 delay(600); // give Python time to register Bridge handlers before first scan 149 discoverDevices(); 150} 151 152void loop() {}
index.html
markup
UI code
1<!doctype html> 2<html lang="en"> 3<head> 4 <meta charset="utf-8" /> 5 <title>Modulino Address Changer</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 <style> 8 :root { 9 --fg: #222; --muted: #777; --accent: #2c7; --warn: #c44; --info: #2479c0; 10 --border: #ddd; --row-alt: #f9f9f9; --fixed-bg: #f4f4f4; 11 } 12 * { box-sizing: border-box; } 13 body { 14 font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; 15 color: var(--fg); background: #fff; margin: 0; padding: 24px 28px; 16 } 17 h2 { margin: 0 0 2px; font-size: 1.35rem; } 18 .subtitle { color: var(--muted); font-size: 13px; margin: 0 0 20px; } 19 20 /* ── Status bar ─────────────────────────────────── */ 21 #statusBar { 22 display: flex; align-items: center; gap: 10px; 23 padding: 9px 14px; border-radius: 7px; margin-bottom: 18px; 24 font-size: 14px; border: 1px solid var(--border); background: #fafafa; 25 min-height: 40px; 26 } 27 #statusBar.ok { border-color: #b2dfc4; background: #f0faf4; color: #1a6b3c; } 28 #statusBar.err { border-color: #f5c0c0; background: #fff5f5; color: var(--warn); } 29 #statusBar.info { border-color: #bcd5f0; background: #f0f6ff; color: var(--info); } 30 #spinner { 31 display: none; width: 16px; height: 16px; flex-shrink: 0; 32 border: 2px solid #ccc; border-top-color: #666; 33 border-radius: 50%; animation: spin 0.7s linear infinite; 34 } 35 @keyframes spin { to { transform: rotate(360deg); } } 36 37 /* ── Top controls ───────────────────────────────── */ 38 .controls { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; } 39 button { 40 padding: 7px 13px; border: 1px solid var(--border); border-radius: 6px; 41 background: #fff; cursor: pointer; font-size: 14px; white-space: nowrap; 42 } 43 button:hover:not(:disabled) { background: #f3f3f3; } 44 button:disabled { opacity: 0.45; cursor: default; } 45 button.primary { background: #1b6e3c; color: #fff; border-color: #1b6e3c; } 46 button.primary:hover:not(:disabled) { background: #155430; } 47 button.danger { color: var(--warn); border-color: #e8aaaa; } 48 button.danger:hover:not(:disabled) { background: #fff0f0; } 49 button.small { padding: 4px 9px; font-size: 13px; } 50 51 /* ── Device table ───────────────────────────────── */ 52 #deviceList { overflow-x: auto; } 53 table { 54 width: 100%; border-collapse: collapse; font-size: 14px; 55 min-width: 560px; 56 } 57 thead th { 58 text-align: left; padding: 8px 12px; border-bottom: 2px solid var(--border); 59 font-size: 12px; text-transform: uppercase; letter-spacing: .05em; 60 color: var(--muted); white-space: nowrap; 61 } 62 tbody tr { border-bottom: 1px solid var(--border); } 63 tbody tr:nth-child(even) { background: var(--row-alt); } 64 tbody tr.fixed-row { color: var(--muted); } 65 tbody tr.fixed-row td { background: var(--fixed-bg); } 66 td { padding: 10px 12px; vertical-align: middle; } 67 code { 68 background: #f0f0f0; padding: 2px 5px; border-radius: 4px; 69 font-size: 13px; white-space: nowrap; 70 } 71 .badge { 72 display: inline-block; padding: 2px 8px; border-radius: 12px; 73 font-size: 12px; font-weight: 600; 74 } 75 .badge-fixed { background: #eee; color: #555; } 76 .badge-default { background: #dff5e8; color: #1b6e3c; } 77 .badge-modified { background: #fef3cd; color: #8a6200; } 78 79 /* ── Address change controls ────────────────────── */ 80 .addr-controls { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; } 81 .addr-input-wrap { position: relative; } 82 .addr-input { 83 width: 90px; padding: 5px 8px; border: 1px solid var(--border); 84 border-radius: 5px; font-size: 13px; font-family: monospace; 85 } 86 .addr-input.valid { border-color: var(--accent); } 87 .addr-input.invalid { border-color: var(--warn); } 88 .addr-input.inuse { border-color: #e09020; } 89 .addr-hint { 90 font-size: 11px; margin-top: 2px; white-space: nowrap; 91 color: var(--muted); 92 } 93 .addr-hint.err { color: var(--warn); } 94 .addr-hint.warn { color: #8a6200; } 95 .addr-hint.ok { color: #1b6e3c; } 96 97 /* ── No-devices placeholder ─────────────────────── */ 98 .empty { 99 padding: 32px; text-align: center; color: var(--muted); 100 border: 1px dashed var(--border); border-radius: 8px; 101 } 102 103 /* ── Duplicate tip ───────────────────────────────── */ 104 details.tip-box { 105 font-size: 13px; padding: 9px 14px; border-radius: 7px; 106 border: 1px solid #bcd5f0; background: #f0f6ff; color: var(--info); 107 margin-bottom: 16px; line-height: 1.5; 108 } 109 details.tip-box summary { 110 cursor: pointer; font-weight: 600; user-select: none; 111 } 112 details.tip-box p { margin: 6px 0 0; } 113 .dup-hint { 114 cursor: help; color: #9ab; font-size: 12px; margin-left: 5px; 115 user-select: none; vertical-align: middle; 116 } 117 118 @media (max-width: 600px) { 119 body { padding: 16px; } 120 .addr-controls { flex-direction: column; align-items: flex-start; } 121 } 122 </style> 123</head> 124<body> 125 126<h2>Modulino Address Changer</h2> 127<p class="subtitle">Discover and reconfigure I²C addresses of connected Modulino devices.</p> 128 129<div id="statusBar" class="info"> 130 <div id="spinner"></div> 131 <span id="statusMsg">Loading…</span> 132</div> 133 134<div class="controls"> 135 <button id="rescanBtn" class="primary" onclick="rescan()">↺ Rescan I²C bus</button> 136 <button id="resetAllBtn" class="danger" onclick="resetAll()">Reset all to default addresses</button> 137</div> 138 139<details class="tip-box"> 140 <summary>ℹ Multiple identical nodes at the same address?</summary> 141 <p> 142 Two nodes of the same type share the same default I²C address and appear as a single 143 entry — address changes will affect both at once. To configure them independently: 144 <strong>disconnect all but one</strong> → click <em>Rescan</em> → change that device's 145 address → reconnect the others and rescan again. 146 </p> 147</details> 148 149<div id="deviceList"></div> 150 151 152<script> 153'use strict'; 154 155let currentDevices = []; 156let busy = false; 157 158// ── API helpers ────────────────────────────────────────────────────────────── 159async function api(method, path, body) { 160 const opts = { method, headers: { 'Content-Type': 'application/json' } }; 161 if (body !== undefined) opts.body = JSON.stringify(body); 162 const r = await fetch(path, opts); 163 if (!r.ok) throw new Error(`HTTP ${r.status}`); 164 return r.json(); 165} 166 167// ── Status bar ─────────────────────────────────────────────────────────────── 168function setStatus(msg, type = 'info', spinning = false) { 169 const bar = document.getElementById('statusBar'); 170 const text = document.getElementById('statusMsg'); 171 const spin = document.getElementById('spinner'); 172 bar.className = type; 173 text.textContent = msg; 174 spin.style.display = spinning ? 'block' : 'none'; 175} 176 177function setBusy(on) { 178 busy = on; 179 document.getElementById('rescanBtn').disabled = on; 180 document.getElementById('resetAllBtn').disabled = on; 181 document.querySelectorAll('.btn-change, .btn-reset').forEach(b => b.disabled = on); 182} 183 184// ── Hex address parser ─────────────────────────────────────────────────────── 185function parseHex(str) { 186 if (!str) return null; 187 str = str.trim().replace(/^0x/i, ''); 188 if (!/^[0-9a-fA-F]{1,2}$/.test(str)) return null; 189 const n = parseInt(str, 16); 190 return (n >= 8 && n <= 0x77) ? n : null; 191} 192 193function fmtHex(n) { 194 return '0x' + n.toString(16).toUpperCase().padStart(2, '0'); 195} 196 197// ── Render ─────────────────────────────────────────────────────────────────── 198function renderDevices(devs) { 199 const container = document.getElementById('deviceList'); 200 201 if (!devs || devs.length === 0) { 202 container.innerHTML = 203 '<div class="empty">No devices found.<br>Check connections and click <strong>Rescan I²C bus</strong>.</div>'; 204 return; 205 } 206 207 // Sort: configurable first, then by address 208 const sorted = [...devs].sort((a, b) => { 209 if (a.configurable !== b.configurable) return a.configurable ? -1 : 1; 210 return a.addr - b.addr; 211 }); 212 213 const table = document.createElement('table'); 214 215 table.innerHTML = ` 216 <thead> 217 <tr> 218 <th>Current Address</th> 219 <th>Node</th> 220 <th>Default Address</th> 221 <th>Status</th> 222 <th>New Address</th> 223 <th>Actions</th> 224 </tr> 225 </thead>`; 226 227 const tbody = document.createElement('tbody'); 228 229 for (const dev of sorted) { 230 const tr = document.createElement('tr'); 231 232 if (!dev.configurable) { 233 tr.className = 'fixed-row'; 234 tr.innerHTML = ` 235 <td><code>${dev.addrHex}</code></td> 236 <td>${dev.name}</td> 237 <td>—</td> 238 <td><span class="badge badge-fixed">Fixed</span></td> 239 <td>—</td> 240 <td>—</td>`; 241 } else { 242 const badgeClass = dev.modified ? 'badge-modified' : 'badge-default'; 243 const badgeText = dev.modified ? `Modified ★` : 'At default'; 244 const defaultInfo = dev.modified 245 ? `Default: <code>${dev.defaultAddrHex}</code>` 246 : `<code>${dev.defaultAddrHex}</code>`; 247 const dupNote = !dev.modified 248 ? `<span class="dup-hint" title="Multiple identical nodes share this address and appear as one entry. Disconnect all but one before changing — see the tip above.">ⓘ</span>` 249 : ''; 250 251 tr.innerHTML = ` 252 <td><code>${dev.addrHex}</code></td> 253 <td><strong>${dev.name}</strong></td> 254 <td>${defaultInfo}</td> 255 <td><span class="badge ${badgeClass}">${badgeText}</span>${dupNote}</td> 256 <td> 257 <div class="addr-input-wrap"> 258 <input 259 id="inp-${dev.addr}" 260 class="addr-input" 261placeholder="e.g. 0x3E" 262 autocomplete="off" 263 oninput="validateInput(${dev.addr})" 264 /> 265 <div id="hint-${dev.addr}" class="addr-hint"></div> 266 </div> 267 </td> 268 <td> 269 <div class="addr-controls"> 270 <button class="small btn-change" onclick="handleChange(${dev.addr})">Change</button> 271 <button 272 class="small btn-reset" 273 onclick="handleReset(${dev.addr})" 274 ${!dev.modified ? 'disabled title="Already at default address"' : ''} 275 >Reset to default</button> 276 </div> 277 </td>`; 278 } 279 280 tbody.appendChild(tr); 281 } 282 283 table.appendChild(tbody); 284 container.innerHTML = ''; 285 container.appendChild(table); 286} 287 288// ── Input validation ───────────────────────────────────────────────────────── 289function validateInput(addr) { 290 const inp = document.getElementById(`inp-${addr}`); 291 const hint = document.getElementById(`hint-${addr}`); 292 const val = inp.value.trim(); 293 294 if (!val) { 295 inp.className = 'addr-input'; 296 hint.textContent = ''; 297 hint.className = 'addr-hint'; 298 return null; 299 } 300 301 const n = parseHex(val); 302 303 if (n === null) { 304 inp.className = 'addr-input invalid'; 305 hint.textContent = 'Must be 0x08–0x77'; 306 hint.className = 'addr-hint err'; 307 return null; 308 } 309 310 if (n === addr) { 311 inp.className = 'addr-input invalid'; 312 hint.textContent = 'Same as current address'; 313 hint.className = 'addr-hint err'; 314 return null; 315 } 316 317 const inUse = currentDevices.find(d => d.addr === n); 318 if (inUse) { 319 inp.className = 'addr-input inuse'; 320 hint.textContent = `In use by ${inUse.name}`; 321 hint.className = 'addr-hint warn'; 322 return n; // still return — user may intentionally want to swap 323 } 324 325 inp.className = 'addr-input valid'; 326 hint.textContent = fmtHex(n) + ' — valid'; 327 hint.className = 'addr-hint ok'; 328 return n; 329} 330 331// ── Actions ────────────────────────────────────────────────────────────────── 332async function loadDevices() { 333 setStatus('Loading devices…', 'info', true); 334 try { 335 const data = await api('GET', '/api/devices'); 336 currentDevices = data.devices || []; 337 renderDevices(currentDevices); 338 const n = currentDevices.length; 339 setStatus(`Found ${n} device${n !== 1 ? 's' : ''}.`, 'ok'); 340 } catch (e) { 341 setStatus('Failed to load devices: ' + e.message, 'err'); 342 } 343} 344 345async function rescan() { 346 setBusy(true); 347 setStatus('Scanning I²C bus…', 'info', true); 348 try { 349 const data = await api('POST', '/api/rescan', {}); 350 currentDevices = data.devices || []; 351 renderDevices(currentDevices); 352 const n = currentDevices.length; 353 setStatus(`Scan complete — found ${n} device${n !== 1 ? 's' : ''}.`, 'ok'); 354 } catch (e) { 355 setStatus('Scan failed: ' + e.message, 'err'); 356 } 357 setBusy(false); 358} 359 360async function doChange(curAddr, newAddr) { 361 setBusy(true); 362 const from = fmtHex(curAddr); 363 const to = newAddr === 0 ? 'default' : fmtHex(newAddr); 364 setStatus(`Changing ${from} → ${to}…`, 'info', true); 365 try { 366 const data = await api('POST', '/api/change_address', 367 { cur_addr: curAddr, new_addr: newAddr }); 368 currentDevices = data.devices || currentDevices; 369 renderDevices(currentDevices); 370 if (data.ok) { 371 setStatus(`Done — ${from} → ${to}. Bus rescanned.`, 'ok'); 372 } else { 373 setStatus(`Change failed: ${data.message || data.error || 'unknown error'}`, 'err'); 374 } 375 } catch (e) { 376 setStatus('Error: ' + e.message, 'err'); 377 } 378 setBusy(false); 379} 380 381function handleChange(addr) { 382 const n = validateInput(addr); 383 if (n === null) { 384 const inp = document.getElementById(`inp-${addr}`); 385 if (!inp.value.trim()) { 386 inp.className = 'addr-input invalid'; 387 const hint = document.getElementById(`hint-${addr}`); 388 hint.textContent = 'Enter a new address first'; 389 hint.className = 'addr-hint err'; 390 } 391 return; 392 } 393 394 const inUse = currentDevices.find(d => d.addr === n); 395 if (inUse) { 396 if (!confirm( 397 `${fmtHex(n)} is currently in use by ${inUse.name}.\n` + 398 `If you proceed, that device may become unreachable.\n\nContinue anyway?` 399 )) return; 400 } 401 402 doChange(addr, n); 403} 404 405function handleReset(addr) { 406 const dev = currentDevices.find(d => d.addr === addr); 407 const defHex = dev ? dev.defaultAddrHex : '?'; 408 if (confirm( 409 `Reset device at ${fmtHex(addr)} to its default address (${defHex})?` 410 )) { 411 doChange(addr, 0); 412 } 413} 414 415function resetAll() { 416 const configurableCount = currentDevices.filter(d => d.configurable).length; 417 if (configurableCount === 0) { 418 alert('No configurable devices found on the bus.'); 419 return; 420 } 421 if (confirm( 422 `Reset ALL ${configurableCount} configurable device${configurableCount !== 1 ? 's' : ''} ` + 423 `to their default addresses?\n\nThis sends a broadcast I²C command.` 424 )) { 425 doChange(0, 0); 426 } 427} 428 429// ── Init ───────────────────────────────────────────────────────────────────── 430loadDevices(); 431</script> 432 433</body> 434</html>
Arduino App Lab
Modulino Change Address
😀
Modulino Change Address
Comments
Only logged in users can leave comments