commit
2fcaeca5c7
|
|
@ -97,3 +97,4 @@ test/esp32-emulator/out_*/
|
||||||
|
|
||||||
# Google Cloud service account credentials
|
# Google Cloud service account credentials
|
||||||
velxio-ba3355a41944.json
|
velxio-ba3355a41944.json
|
||||||
|
marketing/*
|
||||||
|
|
@ -232,8 +232,6 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker)
|
||||||
# ESP32 signal indices: 72-79 = LEDC HS ch 0-7, 80-87 = LEDC LS ch 0-7
|
# ESP32 signal indices: 72-79 = LEDC HS ch 0-7, 80-87 = LEDC LS ch 0-7
|
||||||
_ledc_gpio_map: dict[int, int] = {}
|
_ledc_gpio_map: dict[int, int] = {}
|
||||||
|
|
||||||
_out_sel_dumped = [False] # one-time diagnostic dump flag
|
|
||||||
|
|
||||||
def _refresh_ledc_gpio_map() -> None:
|
def _refresh_ledc_gpio_map() -> None:
|
||||||
"""Scan gpio_out_sel[40] registers and update _ledc_gpio_map.
|
"""Scan gpio_out_sel[40] registers and update _ledc_gpio_map.
|
||||||
|
|
||||||
|
|
@ -243,25 +241,16 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker)
|
||||||
try:
|
try:
|
||||||
out_sel_ptr = lib.qemu_picsimlab_get_internals(2)
|
out_sel_ptr = lib.qemu_picsimlab_get_internals(2)
|
||||||
if not out_sel_ptr:
|
if not out_sel_ptr:
|
||||||
_log('LEDC gpio_out_sel: internals(2) returned NULL')
|
|
||||||
return
|
return
|
||||||
out_sel = (ctypes.c_uint32 * 40).from_address(out_sel_ptr)
|
out_sel = (ctypes.c_uint32 * 40).from_address(out_sel_ptr)
|
||||||
# One-time dump of ALL gpio_out_sel values for diagnostics
|
|
||||||
if not _out_sel_dumped[0]:
|
|
||||||
_out_sel_dumped[0] = True
|
|
||||||
non_default = {pin: int(out_sel[pin]) for pin in range(40)
|
|
||||||
if int(out_sel[pin]) != 256 and int(out_sel[pin]) != 0}
|
|
||||||
_log(f'LEDC gpio_out_sel dump (non-default): {non_default}')
|
|
||||||
_log(f'LEDC gpio_out_sel ALL: {[int(out_sel[i]) for i in range(40)]}')
|
|
||||||
for gpio_pin in range(40):
|
for gpio_pin in range(40):
|
||||||
signal = int(out_sel[gpio_pin]) & 0xFF
|
signal = int(out_sel[gpio_pin]) & 0xFF
|
||||||
if 72 <= signal <= 87:
|
if 72 <= signal <= 87:
|
||||||
ledc_ch = signal - 72
|
ledc_ch = signal - 72
|
||||||
if _ledc_gpio_map.get(ledc_ch) != gpio_pin:
|
if _ledc_gpio_map.get(ledc_ch) != gpio_pin:
|
||||||
_ledc_gpio_map[ledc_ch] = gpio_pin
|
_ledc_gpio_map[ledc_ch] = gpio_pin
|
||||||
_log(f'LEDC map: ch{ledc_ch} -> GPIO{gpio_pin} (signal={signal})')
|
except Exception:
|
||||||
except Exception as e:
|
pass
|
||||||
_log(f'LEDC gpio_out_sel scan error: {e}')
|
|
||||||
|
|
||||||
# Sensor state: gpio_pin → {type, properties..., saw_low, responding}
|
# Sensor state: gpio_pin → {type, properties..., saw_low, responding}
|
||||||
_sensors: dict[int, dict] = {}
|
_sensors: dict[int, dict] = {}
|
||||||
|
|
@ -456,7 +445,6 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker)
|
||||||
if marker == 0x5000: # LEDC duty change (from esp32_ledc.c)
|
if marker == 0x5000: # LEDC duty change (from esp32_ledc.c)
|
||||||
ledc_ch = (direction >> 8) & 0x0F
|
ledc_ch = (direction >> 8) & 0x0F
|
||||||
intensity = direction & 0xFF # 0-100 percentage
|
intensity = direction & 0xFF # 0-100 percentage
|
||||||
_log(f'0x5000 callback: direction=0x{direction:04X} ch={ledc_ch} intensity={intensity} map={dict(_ledc_gpio_map)}')
|
|
||||||
gpio = _ledc_gpio_map.get(ledc_ch, -1)
|
gpio = _ledc_gpio_map.get(ledc_ch, -1)
|
||||||
if gpio == -1:
|
if gpio == -1:
|
||||||
_refresh_ledc_gpio_map()
|
_refresh_ledc_gpio_map()
|
||||||
|
|
@ -622,52 +610,13 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker)
|
||||||
def _ledc_poll_thread() -> None:
|
def _ledc_poll_thread() -> None:
|
||||||
# Track last-emitted duty to avoid flooding identical updates
|
# Track last-emitted duty to avoid flooding identical updates
|
||||||
_last_duty = [0.0] * 16
|
_last_duty = [0.0] * 16
|
||||||
_diag_count = [0]
|
|
||||||
_first_nonzero_logged = [False]
|
|
||||||
_log('LEDC poll thread started')
|
|
||||||
while not _stopped.wait(0.1):
|
while not _stopped.wait(0.1):
|
||||||
try:
|
try:
|
||||||
ptr = lib.qemu_picsimlab_get_internals(6) # LEDC_CHANNEL_DUTY
|
ptr = lib.qemu_picsimlab_get_internals(6) # LEDC_CHANNEL_DUTY
|
||||||
_diag_count[0] += 1
|
|
||||||
if ptr is None or ptr == 0:
|
if ptr is None or ptr == 0:
|
||||||
continue
|
continue
|
||||||
# duty[] is float[16] in QEMU (percentage 0-100)
|
|
||||||
arr = (ctypes.c_float * 16).from_address(ptr)
|
arr = (ctypes.c_float * 16).from_address(ptr)
|
||||||
# Refresh LEDC→GPIO mapping from gpio_out_sel[40] registers
|
|
||||||
_refresh_ledc_gpio_map()
|
_refresh_ledc_gpio_map()
|
||||||
# Log once when nonzero duties first appear
|
|
||||||
if not _first_nonzero_logged[0]:
|
|
||||||
nonzero = {ch: round(float(arr[ch]), 2) for ch in range(16)
|
|
||||||
if float(arr[ch]) != 0.0}
|
|
||||||
if nonzero:
|
|
||||||
_log(f'LEDC first nonzero at poll #{_diag_count[0]}: '
|
|
||||||
f'duties={nonzero} gpio_map={dict(_ledc_gpio_map)}')
|
|
||||||
_first_nonzero_logged[0] = True
|
|
||||||
# Periodic diagnostic dump every 50 polls (~5s)
|
|
||||||
if _diag_count[0] % 50 == 0:
|
|
||||||
all_duties = {ch: round(float(arr[ch]), 4) for ch in range(16)
|
|
||||||
if float(arr[ch]) != 0.0}
|
|
||||||
# Also read LEDC channel conf (internals(4)) and timer freq (internals(5))
|
|
||||||
diag_parts = [f'duties={all_duties}', f'gpio_map={dict(_ledc_gpio_map)}']
|
|
||||||
try:
|
|
||||||
conf_ptr = lib.qemu_picsimlab_get_internals(4)
|
|
||||||
if conf_ptr:
|
|
||||||
conf_arr = (ctypes.c_uint32 * 16).from_address(conf_ptr)
|
|
||||||
nonzero_conf = {ch: hex(int(conf_arr[ch])) for ch in range(16)
|
|
||||||
if int(conf_arr[ch]) != 0}
|
|
||||||
diag_parts.append(f'ch_conf={nonzero_conf}')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
freq_ptr = lib.qemu_picsimlab_get_internals(5)
|
|
||||||
if freq_ptr:
|
|
||||||
freq_arr = (ctypes.c_uint32 * 8).from_address(freq_ptr)
|
|
||||||
nonzero_freq = {t: int(freq_arr[t]) for t in range(8)
|
|
||||||
if int(freq_arr[t]) != 0}
|
|
||||||
diag_parts.append(f'timer_freq={nonzero_freq}')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
_log(f'LEDC poll #{_diag_count[0]}: {" | ".join(diag_parts)}')
|
|
||||||
for ch in range(16):
|
for ch in range(16):
|
||||||
duty_pct = float(arr[ch])
|
duty_pct = float(arr[ch])
|
||||||
if abs(duty_pct - _last_duty[ch]) < 0.01:
|
if abs(duty_pct - _last_duty[ch]) < 0.01:
|
||||||
|
|
@ -679,9 +628,8 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker)
|
||||||
'duty': round(duty_pct, 2),
|
'duty': round(duty_pct, 2),
|
||||||
'duty_pct': round(duty_pct, 2),
|
'duty_pct': round(duty_pct, 2),
|
||||||
'gpio': gpio})
|
'gpio': gpio})
|
||||||
except Exception as e:
|
except Exception:
|
||||||
import traceback
|
pass
|
||||||
_log(f'LEDC poll error: {e}\n{traceback.format_exc()}')
|
|
||||||
|
|
||||||
threading.Thread(target=_ledc_poll_thread, daemon=True, name='ledc-poll').start()
|
threading.Thread(target=_ledc_poll_thread, daemon=True, name='ledc-poll').start()
|
||||||
|
|
||||||
|
|
@ -706,7 +654,6 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker)
|
||||||
raw_v = int(int(cmd['millivolts']) * 4095 / 3300)
|
raw_v = int(int(cmd['millivolts']) * 4095 / 3300)
|
||||||
ch = int(cmd['channel'])
|
ch = int(cmd['channel'])
|
||||||
clamped = max(0, min(4095, raw_v))
|
clamped = max(0, min(4095, raw_v))
|
||||||
_log(f'set_adc: ch={ch} mv={cmd["millivolts"]} raw={clamped}')
|
|
||||||
lib.qemu_picsimlab_set_apin(ch, clamped)
|
lib.qemu_picsimlab_set_apin(ch, clamped)
|
||||||
|
|
||||||
elif c == 'set_adc_raw':
|
elif c == 'set_adc_raw':
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,34 @@
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Libraries */
|
/* Libraries button (always visible, with label) */
|
||||||
|
.tb-btn-libraries {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 4px 10px 4px 8px;
|
||||||
|
border: 1px solid rgba(0, 184, 212, 0.3);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: rgba(0, 184, 212, 0.08);
|
||||||
|
color: #00e5ff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.tb-btn-libraries:hover {
|
||||||
|
background: rgba(0, 184, 212, 0.18);
|
||||||
|
border-color: rgba(0, 184, 212, 0.5);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.tb-libraries-label {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy lib icon-only button (for overflow items) */
|
||||||
.tb-btn-lib {
|
.tb-btn-lib {
|
||||||
color: #00b8d4;
|
color: #00b8d4;
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +124,52 @@
|
||||||
color: #00e5ff;
|
color: #00e5ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Missing library hint banner */
|
||||||
|
.tb-lib-hint {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
background: rgba(255, 152, 0, 0.1);
|
||||||
|
border-bottom: 1px solid rgba(255, 152, 0, 0.25);
|
||||||
|
color: #ffb74d;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.tb-lib-hint svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #ffa726;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-btn {
|
||||||
|
background: rgba(0, 184, 212, 0.15);
|
||||||
|
border: 1px solid rgba(0, 184, 212, 0.4);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #00e5ff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
padding: 2px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-btn:hover {
|
||||||
|
background: rgba(0, 184, 212, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-close {
|
||||||
|
margin-left: auto;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #888;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 4px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-close:hover {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
/* Output Console toggle */
|
/* Output Console toggle */
|
||||||
.tb-btn-output {
|
.tb-btn-output {
|
||||||
color: #9d9d9d;
|
color: #9d9d9d;
|
||||||
|
|
|
||||||
|
|
@ -67,20 +67,12 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
const [installModalOpen, setInstallModalOpen] = useState(false);
|
const [installModalOpen, setInstallModalOpen] = useState(false);
|
||||||
const importInputRef = useRef<HTMLInputElement>(null);
|
const importInputRef = useRef<HTMLInputElement>(null);
|
||||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||||
const [overflowCollapsed, setOverflowCollapsed] = useState(false);
|
|
||||||
const [overflowOpen, setOverflowOpen] = useState(false);
|
const [overflowOpen, setOverflowOpen] = useState(false);
|
||||||
const overflowMenuRef = useRef<HTMLDivElement>(null);
|
const overflowMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [missingLibHint, setMissingLibHint] = useState(false);
|
||||||
|
|
||||||
// Collapse secondary buttons when toolbar is too narrow
|
// (ResizeObserver removed — Library Manager is always visible now,
|
||||||
useEffect(() => {
|
// only import/export live in the overflow menu)
|
||||||
const el = toolbarRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
const ro = new ResizeObserver(([entry]) => {
|
|
||||||
setOverflowCollapsed(entry.contentRect.width < 500);
|
|
||||||
});
|
|
||||||
ro.observe(el);
|
|
||||||
return () => ro.disconnect();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Close overflow dropdown on outside click
|
// Close overflow dropdown on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -143,8 +135,14 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
compileBoardProgram(activeBoardId, program);
|
compileBoardProgram(activeBoardId, program);
|
||||||
}
|
}
|
||||||
setMessage({ type: 'success', text: 'Compiled successfully' });
|
setMessage({ type: 'success', text: 'Compiled successfully' });
|
||||||
|
setMissingLibHint(false);
|
||||||
} else {
|
} else {
|
||||||
setMessage({ type: 'error', text: result.error || result.stderr || 'Compile failed' });
|
const errText = result.error || result.stderr || 'Compile failed';
|
||||||
|
setMessage({ type: 'error', text: errText });
|
||||||
|
// Detect missing library errors — common patterns:
|
||||||
|
// "No such file or directory" for #include, "fatal error: XXX.h"
|
||||||
|
const looksLikeMissingLib = /No such file or directory|fatal error:.*\.h|library not found/i.test(errText);
|
||||||
|
setMissingLibHint(looksLikeMissingLib);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = err instanceof Error ? err.message : 'Compile failed';
|
const errMsg = err instanceof Error ? err.message : 'Compile failed';
|
||||||
|
|
@ -428,104 +426,61 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
onChange={handleImportFile}
|
onChange={handleImportFile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Secondary buttons — hidden when toolbar is narrow */}
|
{/* Library Manager — always visible with label */}
|
||||||
{!overflowCollapsed && (
|
<button
|
||||||
<>
|
onClick={() => setLibManagerOpen(true)}
|
||||||
{/* Import Wokwi zip */}
|
className="tb-btn-libraries"
|
||||||
<button
|
title="Search and install Arduino libraries"
|
||||||
onClick={() => importInputRef.current?.click()}
|
>
|
||||||
className="tb-btn tb-btn-lib"
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
title="Import Wokwi zip"
|
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" />
|
||||||
>
|
<path d="m3.3 7 8.7 5 8.7-5" />
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<path d="M12 22V12" />
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
</svg>
|
||||||
<polyline points="7 10 12 15 17 10" />
|
<span className="tb-libraries-label">Libraries</span>
|
||||||
<line x1="12" y1="15" x2="12" y2="3" />
|
</button>
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Export Wokwi zip */}
|
{/* Import / Export — overflow menu */}
|
||||||
<button
|
<div className="tb-overflow-wrap" ref={overflowMenuRef}>
|
||||||
onClick={handleExport}
|
<button
|
||||||
className="tb-btn tb-btn-lib"
|
onClick={() => setOverflowOpen((v) => !v)}
|
||||||
title="Export as Wokwi zip"
|
className={`tb-btn tb-btn-overflow${overflowOpen ? ' tb-btn-overflow-active' : ''}`}
|
||||||
>
|
title="Import / Export"
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
>
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||||
<polyline points="17 8 12 3 7 8" />
|
<circle cx="5" cy="12" r="2" />
|
||||||
<line x1="12" y1="3" x2="12" y2="15" />
|
<circle cx="12" cy="12" r="2" />
|
||||||
</svg>
|
<circle cx="19" cy="12" r="2" />
|
||||||
</button>
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Libraries */}
|
{overflowOpen && (
|
||||||
<button
|
<div className="tb-overflow-menu">
|
||||||
onClick={() => setLibManagerOpen(true)}
|
<button
|
||||||
className="tb-btn tb-btn-lib"
|
className="tb-overflow-item"
|
||||||
title="Library Manager"
|
onClick={() => { importInputRef.current?.click(); setOverflowOpen(false); }}
|
||||||
>
|
>
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" />
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||||
<path d="m3.3 7 8.7 5 8.7-5" />
|
<polyline points="7 10 12 15 17 10" />
|
||||||
<path d="M12 22V12" />
|
<line x1="12" y1="15" x2="12" y2="3" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
Import zip
|
||||||
</>
|
</button>
|
||||||
)}
|
<button
|
||||||
|
className="tb-overflow-item"
|
||||||
{/* Overflow (…) button — shown when toolbar is narrow */}
|
onClick={() => { handleExport(); setOverflowOpen(false); }}
|
||||||
{overflowCollapsed && (
|
>
|
||||||
<div className="tb-overflow-wrap" ref={overflowMenuRef}>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<button
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||||
onClick={() => setOverflowOpen((v) => !v)}
|
<polyline points="17 8 12 3 7 8" />
|
||||||
className={`tb-btn tb-btn-overflow${overflowOpen ? ' tb-btn-overflow-active' : ''}`}
|
<line x1="12" y1="3" x2="12" y2="15" />
|
||||||
title="More actions"
|
</svg>
|
||||||
>
|
Export zip
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
</button>
|
||||||
<circle cx="5" cy="12" r="2" />
|
</div>
|
||||||
<circle cx="12" cy="12" r="2" />
|
)}
|
||||||
<circle cx="19" cy="12" r="2" />
|
</div>
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{overflowOpen && (
|
|
||||||
<div className="tb-overflow-menu">
|
|
||||||
<button
|
|
||||||
className="tb-overflow-item"
|
|
||||||
onClick={() => { importInputRef.current?.click(); setOverflowOpen(false); }}
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
||||||
<polyline points="7 10 12 15 17 10" />
|
|
||||||
<line x1="12" y1="15" x2="12" y2="3" />
|
|
||||||
</svg>
|
|
||||||
Import Wokwi zip
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="tb-overflow-item"
|
|
||||||
onClick={() => { handleExport(); setOverflowOpen(false); }}
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
||||||
<polyline points="17 8 12 3 7 8" />
|
|
||||||
<line x1="12" y1="3" x2="12" y2="15" />
|
|
||||||
</svg>
|
|
||||||
Export as Wokwi zip
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="tb-overflow-item"
|
|
||||||
onClick={() => { setLibManagerOpen(true); setOverflowOpen(false); }}
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" />
|
|
||||||
<path d="m3.3 7 8.7 5 8.7-5" />
|
|
||||||
<path d="M12 22V12" />
|
|
||||||
</svg>
|
|
||||||
Library Manager
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="tb-divider" />
|
<div className="tb-divider" />
|
||||||
|
|
||||||
|
|
@ -549,6 +504,24 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
<div className="toolbar-error-detail">{message.text}</div>
|
<div className="toolbar-error-detail">{message.text}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Missing library hint */}
|
||||||
|
{missingLibHint && (
|
||||||
|
<div className="tb-lib-hint">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
|
</svg>
|
||||||
|
<span>Missing library? Install it from the</span>
|
||||||
|
<button className="tb-lib-hint-btn" onClick={() => { setLibManagerOpen(true); setMissingLibHint(false); }}>
|
||||||
|
Library Manager
|
||||||
|
</button>
|
||||||
|
<button className="tb-lib-hint-close" onClick={() => setMissingLibHint(false)} title="Dismiss">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<LibraryManagerModal isOpen={libManagerOpen} onClose={() => setLibManagerOpen(false)} />
|
<LibraryManagerModal isOpen={libManagerOpen} onClose={() => setLibManagerOpen(false)} />
|
||||||
<InstallLibrariesModal
|
<InstallLibrariesModal
|
||||||
isOpen={installModalOpen}
|
isOpen={installModalOpen}
|
||||||
|
|
|
||||||
|
|
@ -670,7 +670,7 @@ void drawStaticUI() {
|
||||||
tft.setTextSize(3);
|
tft.setTextSize(3);
|
||||||
tft.setTextColor(tft.color565(255, 220, 0));
|
tft.setTextColor(tft.color565(255, 220, 0));
|
||||||
tft.setCursor(20, 10);
|
tft.setCursor(20, 10);
|
||||||
tft.print("WOKWI TFT");
|
tft.print("VELXIO TFT");
|
||||||
|
|
||||||
// Subtitle
|
// Subtitle
|
||||||
tft.setTextSize(2);
|
tft.setTextSize(2);
|
||||||
|
|
@ -760,7 +760,7 @@ void setup() {
|
||||||
// Print a message to the LCD.
|
// Print a message to the LCD.
|
||||||
lcd.print("Hello, Arduino!");
|
lcd.print("Hello, Arduino!");
|
||||||
lcd.setCursor(0, 1);
|
lcd.setCursor(0, 1);
|
||||||
lcd.print("Wokwi Emulator");
|
lcd.print("Velxio Emulator");
|
||||||
lcd.setCursor(0, 2);
|
lcd.setCursor(0, 2);
|
||||||
lcd.print("LCD 2004 Test");
|
lcd.print("LCD 2004 Test");
|
||||||
}
|
}
|
||||||
|
|
@ -2034,7 +2034,6 @@ void loop() {
|
||||||
// Blinks the built-in LED (GPIO2) and an external LED (GPIO4)
|
// Blinks the built-in LED (GPIO2) and an external LED (GPIO4)
|
||||||
// Requires arduino-esp32 2.0.17 (IDF 4.4.x) — see docs/ESP32_EMULATION.md
|
// Requires arduino-esp32 2.0.17 (IDF 4.4.x) — see docs/ESP32_EMULATION.md
|
||||||
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -2042,7 +2041,6 @@ void loop() {
|
||||||
#define LED_EXT_PIN 4 // External red LED
|
#define LED_EXT_PIN 4 // External red LED
|
||||||
|
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -2092,12 +2090,10 @@ void loop() {
|
||||||
// Echoes anything received on Serial (UART0) back to the sender.
|
// Echoes anything received on Serial (UART0) back to the sender.
|
||||||
// Open the Serial Monitor, type something, and see it echoed back.
|
// Open the Serial Monitor, type something, and see it echoed back.
|
||||||
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -2937,7 +2933,6 @@ void loop() {
|
||||||
code: `// ESP32 — 7-Segment Display Counter 0-9
|
code: `// ESP32 — 7-Segment Display Counter 0-9
|
||||||
// Segments: a=12, b=13, c=14, d=25, e=26, f=27, g=32
|
// Segments: a=12, b=13, c=14, d=25, e=26, f=27, g=32
|
||||||
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -2957,7 +2952,6 @@ const bool DIGITS[10][7] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -3757,7 +3751,6 @@ void loop() {
|
||||||
// Wiring: DATA → GPIO4 | VCC → 3V3 | GND → GND
|
// Wiring: DATA → GPIO4 | VCC → 3V3 | GND → GND
|
||||||
|
|
||||||
#include <DHT.h>
|
#include <DHT.h>
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -3768,7 +3761,6 @@ DHT dht(DHT_PIN, DHT_TYPE);
|
||||||
|
|
||||||
// Disable hardware Timer Group WDTs (QEMU emulation is slower than real time)
|
// Disable hardware Timer Group WDTs (QEMU emulation is slower than real time)
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -3818,7 +3810,6 @@ void loop() {
|
||||||
code: `// ESP32 — HC-SR04 Ultrasonic Distance Sensor
|
code: `// ESP32 — HC-SR04 Ultrasonic Distance Sensor
|
||||||
// Wiring: TRIG → D18 | ECHO → D19 | VCC → 3V3 | GND → GND
|
// Wiring: TRIG → D18 | ECHO → D19 | VCC → 3V3 | GND → GND
|
||||||
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -3826,7 +3817,6 @@ void loop() {
|
||||||
#define ECHO_PIN 19
|
#define ECHO_PIN 19
|
||||||
|
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -3882,7 +3872,6 @@ void loop() {
|
||||||
// Requires: Adafruit MPU6050, Adafruit Unified Sensor libraries
|
// Requires: Adafruit MPU6050, Adafruit Unified Sensor libraries
|
||||||
// Wiring: SDA → D21 | SCL → D22 | VCC → 3V3 | GND → GND
|
// Wiring: SDA → D21 | SCL → D22 | VCC → 3V3 | GND → GND
|
||||||
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
#include <Adafruit_MPU6050.h>
|
#include <Adafruit_MPU6050.h>
|
||||||
|
|
@ -3892,7 +3881,6 @@ void loop() {
|
||||||
Adafruit_MPU6050 mpu;
|
Adafruit_MPU6050 mpu;
|
||||||
|
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -3948,7 +3936,6 @@ void loop() {
|
||||||
code: `// ESP32 — PIR Motion Sensor
|
code: `// ESP32 — PIR Motion Sensor
|
||||||
// Wiring: OUT → D5 | VCC → 3V3 | GND → GND
|
// Wiring: OUT → D5 | VCC → 3V3 | GND → GND
|
||||||
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -3959,7 +3946,6 @@ bool prevMotion = false;
|
||||||
unsigned long detections = 0;
|
unsigned long detections = 0;
|
||||||
|
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -4015,7 +4001,6 @@ void loop() {
|
||||||
// Pot : SIG → D34 | VCC → 3V3 | GND → GND
|
// Pot : SIG → D34 | VCC → 3V3 | GND → GND
|
||||||
|
|
||||||
#include <ESP32Servo.h>
|
#include <ESP32Servo.h>
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -4026,7 +4011,6 @@ Servo myServo;
|
||||||
|
|
||||||
// Disable hardware Timer Group WDTs (QEMU emulation is slower than real time)
|
// Disable hardware Timer Group WDTs (QEMU emulation is slower than real time)
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -4075,7 +4059,6 @@ void loop() {
|
||||||
// Wiring: HORZ → D35 | VERT → D34 | SEL → D15
|
// Wiring: HORZ → D35 | VERT → D34 | SEL → D15
|
||||||
// VCC → 3V3 | GND → GND
|
// VCC → 3V3 | GND → GND
|
||||||
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -4084,7 +4067,6 @@ void loop() {
|
||||||
#define JOY_BTN 15 // GPIO with pull-up
|
#define JOY_BTN 15 // GPIO with pull-up
|
||||||
|
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
@ -4139,7 +4121,6 @@ void loop() {
|
||||||
// Wiring: DATA → GPIO3 | VCC → 3V3 | GND → GND
|
// Wiring: DATA → GPIO3 | VCC → 3V3 | GND → GND
|
||||||
|
|
||||||
#include <DHT.h>
|
#include <DHT.h>
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <soc/timer_group_struct.h>
|
#include <soc/timer_group_struct.h>
|
||||||
#include <soc/timer_group_reg.h>
|
#include <soc/timer_group_reg.h>
|
||||||
|
|
||||||
|
|
@ -4150,7 +4131,6 @@ DHT dht(DHT_PIN, DHT_TYPE);
|
||||||
|
|
||||||
// Disable hardware Timer Group WDTs (QEMU emulation is slower than real time)
|
// Disable hardware Timer Group WDTs (QEMU emulation is slower than real time)
|
||||||
void disableAllWDT() {
|
void disableAllWDT() {
|
||||||
esp_task_wdt_deinit();
|
|
||||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||||
TIMERG0.wdt_config0.en = 0;
|
TIMERG0.wdt_config0.en = 0;
|
||||||
TIMERG0.wdt_wprotect = 0;
|
TIMERG0.wdt_wprotect = 0;
|
||||||
|
|
|
||||||
|
|
@ -71,12 +71,9 @@ PartSimulationRegistry.register('potentiometer', {
|
||||||
const isESP32 = typeof (simulator as any).setAdcVoltage === 'function';
|
const isESP32 = typeof (simulator as any).setAdcVoltage === 'function';
|
||||||
const refVoltage = (isRP2040 || isESP32) ? 3.3 : 5.0;
|
const refVoltage = (isRP2040 || isESP32) ? 3.3 : 5.0;
|
||||||
|
|
||||||
console.log(`[Pot] attached: pin=${pin} isESP32=${isESP32} refV=${refVoltage}`);
|
|
||||||
|
|
||||||
const onInput = () => {
|
const onInput = () => {
|
||||||
const raw = parseInt((element as any).value || '0', 10);
|
const raw = parseInt((element as any).value || '0', 10);
|
||||||
const volts = (raw / 1023.0) * refVoltage;
|
const volts = (raw / 1023.0) * refVoltage;
|
||||||
console.log(`[Pot] onInput: raw=${raw} volts=${volts.toFixed(3)} pin=${pin}`);
|
|
||||||
setAdcVoltage(simulator, pin, volts);
|
setAdcVoltage(simulator, pin, volts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -338,14 +335,11 @@ PartSimulationRegistry.register('servo', {
|
||||||
// 544µs = 2.72%, 2400µs = 12.0%
|
// 544µs = 2.72%, 2400µs = 12.0%
|
||||||
const MIN_DC = MIN_PULSE_US / 20000; // 0.0272
|
const MIN_DC = MIN_PULSE_US / 20000; // 0.0272
|
||||||
const MAX_DC = MAX_PULSE_US / 20000; // 0.12
|
const MAX_DC = MAX_PULSE_US / 20000; // 0.12
|
||||||
console.log(`[Servo:ESP32] registering onPwmChange on pin=${pinSIG}`);
|
|
||||||
const unsubscribe = pinManager.onPwmChange(pinSIG, (_pin, dutyCycle) => {
|
const unsubscribe = pinManager.onPwmChange(pinSIG, (_pin, dutyCycle) => {
|
||||||
console.log(`[Servo:ESP32] onPwmChange pin=${_pin} dutyCycle=${dutyCycle.toFixed(4)}`);
|
|
||||||
if (dutyCycle < 0.01 || dutyCycle > 0.20) return; // ignore out-of-range
|
if (dutyCycle < 0.01 || dutyCycle > 0.20) return; // ignore out-of-range
|
||||||
const angle = Math.round(
|
const angle = Math.round(
|
||||||
((dutyCycle - MIN_DC) / (MAX_DC - MIN_DC)) * 180
|
((dutyCycle - MIN_DC) / (MAX_DC - MIN_DC)) * 180
|
||||||
);
|
);
|
||||||
console.log(`[Servo:ESP32] angle=${angle}`);
|
|
||||||
el.angle = Math.max(0, Math.min(180, angle));
|
el.angle = Math.max(0, Math.min(180, angle));
|
||||||
});
|
});
|
||||||
return () => { unsubscribe(); };
|
return () => { unsubscribe(); };
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ export function setAdcVoltage(simulator: AnySimulator, pin: number, voltage: num
|
||||||
const channel = pin - 26;
|
const channel = pin - 26;
|
||||||
// RP2040 ADC: 12-bit, 3.3V reference
|
// RP2040 ADC: 12-bit, 3.3V reference
|
||||||
const adcValue = Math.round((voltage / 3.3) * 4095);
|
const adcValue = Math.round((voltage / 3.3) * 4095);
|
||||||
console.log(`[setAdcVoltage] RP2040 ch${channel} = ${adcValue} (${voltage.toFixed(3)}V)`);
|
|
||||||
simulator.setADCValue(channel, adcValue);
|
simulator.setADCValue(channel, adcValue);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -862,6 +862,12 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
bridge.onLedcUpdate = makeLedcUpdateHandler(boardId);
|
bridge.onLedcUpdate = makeLedcUpdateHandler(boardId);
|
||||||
|
bridge.onWs2812Update = (channel, pixels) => {
|
||||||
|
const eventTarget = document.getElementById(`ws2812-${boardId}-${channel}`);
|
||||||
|
if (eventTarget) {
|
||||||
|
eventTarget.dispatchEvent(new CustomEvent('ws2812-pixels', { detail: { pixels } }));
|
||||||
|
}
|
||||||
|
};
|
||||||
esp32BridgeMap.set(boardId, bridge);
|
esp32BridgeMap.set(boardId, bridge);
|
||||||
const shim = new Esp32BridgeShim(bridge, pm);
|
const shim = new Esp32BridgeShim(bridge, pm);
|
||||||
shim.onSerialData = serialCallback;
|
shim.onSerialData = serialCallback;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue