@dev-1o/simulator
v0.1.1
Published
Arduino 2D circuit simulator engine, React studio, and Wokwi element registration for Dev.io
Maintainers
Readme
@dev-1o/simulator
Paket npm untuk menyematkan simulator Arduino 2D ke aplikasi Anda.
Dengan paket ini, pengguna bisa:
- menaruh komponen (Arduino Uno, LED, resistor, tombol, sensor, …)
- menarik kabel antar pin
- menulis sketch C++ (
setup/loop) - menekan Mulai dan melihat rangkaian tersimulasi di browser
Tidak perlu Arduino fisik. Tidak perlu arduino-cli. Semuanya berjalan di halaman web.
Daftar isi
- Pilih cara pakai
- Instalasi
- Tiga baris yang wajib diimpor
- Cara A — Studio utuh (paling mudah)
- Cara B — Sandbox saja (tanpa modul praktikum)
- Cara C — Rakit UI sendiri
- Cara D — Engine tanpa React
- Props
Studio/useSimulator - Hasil
useSimulator() - Komponen React yang diekspor
- Tipe data rangkaian
- Komponen yang tersedia
- Impor / ekspor proyek JSON
- CSS dan tabrakan gaya
- Entry map
- Yang belum didukung
- Keamanan
- Autocomplete TypeScript
- Contoh Vite + React
- Lisensi
1. Pilih cara pakai
| Anda ingin | Pakai |
| --- | --- |
| Aplikasi belajar lengkap (modul + kanvas + editor) | <Studio /> |
| Kanvas bebas saja, tanpa praktikum | <Studio learningMode={false} /> |
| Layout sendiri, logika simulator tetap dari paket | useSimulator() + komponen |
| Backend / Node / UI custom total | ArduinoRuntime + solveCircuitNetlist |
Kalau ragu: mulai dari Cara A, lalu sederhanakan ke Cara B jika tidak butuh modul.
2. Instalasi
Hanya engine (tanpa UI)
npm install @dev-1o/simulatorTidak perlu React.
Dengan UI React (Studio / kanvas / editor)
npm install @dev-1o/simulator react react-dom lucide-react @wokwi/elements| Paket | Kenapa diperlukan |
| --- | --- |
| @dev-1o/simulator | Engine + komponen Studio |
| react / react-dom | UI Studio. React 19+ |
| lucide-react | Ikon di header, toolbox, modal |
| @wokwi/elements | Gambar komponen (Arduino, LED, …) |
React, Lucide, dan Wokwi adalah peer dependency opsional. Engine tetap jalan tanpa mereka. UI React tidak jalan tanpa mereka.
Versi yang dicoba: React >=19, @wokwi/elements ^1.9.2.
3. Tiga baris yang wajib diimpor
Setiap kali Anda merender UI dari paket ini, impor ketiga baris ini sekali di pintu masuk aplikasi (misalnya main.tsx):
import '@dev-1o/simulator/style.css';
import '@dev-1o/simulator/elements';Lalu impor komponen:
import { Studio } from '@dev-1o/simulator/react';Tanpa style.css, tombol dan layout terlihat polos / rusak.
Tanpa elements, papan Arduino dan LED tidak tergambar.
4. Cara A — Studio utuh (paling mudah)
Ini menampilkan workspace lengkap seperti situs Dev.io: header, toolbox, kanvas, editor sketch, Serial Monitor, dan panel praktikum.
import { Studio } from '@dev-1o/simulator/react';
import '@dev-1o/simulator/style.css';
import '@dev-1o/simulator/elements';
export function App() {
return <Studio />;
}Default:
- mode Belajar menyala
- rangkaian awal = modul 1 (LED berkedip)
- kode awal = sketch modul 1
Studio mengisi layar penuh (100vw × 100vh). Bungkus di halaman yang tingginya jelas, misalnya:
<div style={{ height: '100vh' }}>
<Studio />
</div>5. Cara B — Sandbox saja (tanpa modul praktikum)
Pakai ini jika aplikasi Anda hanya butuh playground rangkaian, bukan kursus.
import { Studio } from '@dev-1o/simulator/react';
import '@dev-1o/simulator/style.css';
import '@dev-1o/simulator/elements';
const SKETCH_KOSONG = `void setup() {
}
void loop() {
}
`;
export function SandboxPage() {
return (
<Studio
learningMode={false}
initialCircuit={{ components: [], wires: [] }}
initialCode={SKETCH_KOSONG}
/>
);
}Penjelasan opsi:
| Prop | Efek |
| --- | --- |
| learningMode={false} | Panel Praktikum tidak ditampilkan. Toolbox tidak membatasi jumlah komponen. |
| initialCircuit={{ components: [], wires: [] }} | Kanvas mulai kosong. Jika dihilangkan, kanvas terisi rangkaian modul 1. |
| initialCode | Isi editor sketch.ino saat pertama dibuka. |
Header masih punya tombol Belajar / Sandbox. Pengguna bisa menyalakan mode belajar lagi dari UI. Jika Anda ingin mengunci sandbox (tidak bisa kembali ke praktikum), pakai Cara C dan jangan render Header default, atau buat header sendiri tanpa toggle itu.
6. Cara C — Rakit UI sendiri
useSimulator() memegang seluruh state: rangkaian, kode, serial, jalan/berhenti. Anda hanya menyusun layout.
import {
useSimulator,
CircuitCanvas,
Toolbox,
CodeEditor,
SerialMonitor,
} from '@dev-1o/simulator/react';
import '@dev-1o/simulator/style.css';
import '@dev-1o/simulator/elements';
export function CustomSandbox() {
const sim = useSimulator({
learningMode: false,
initialCircuit: { components: [], wires: [] },
});
return (
<div style={{ display: 'flex', height: '100vh' }}>
<Toolbox onAddComponent={sim.addComponent} />
<div style={{ flex: 1, minWidth: 0 }}>
<button type="button" onClick={() => void sim.start()}>
Mulai
</button>
<button type="button" onClick={sim.stop}>
Stop
</button>
<CircuitCanvas
components={sim.circuit.components}
wires={sim.circuit.wires}
selectedComponentId={sim.selectedComponentId}
selectedWireId={sim.selectedWireId}
activeWireColor={sim.activeWireColor}
onSelectWireColor={sim.setActiveWireColor}
onSelectComponent={sim.setSelectedComponentId}
onSelectWire={sim.setSelectedWireId}
onUpdateComponentPosition={sim.updateComponentPosition}
onRotateComponent={sim.rotateComponent}
onDeleteComponent={sim.deleteComponent}
onOpenProperties={sim.setEditingComponent}
onUpdateComponentProperty={sim.updateComponentProperty}
onAddWire={sim.addWire}
onDeleteWire={sim.deleteWire}
onLoadStarter={sim.resetStarter}
/>
</div>
<div
style={{
width: 380,
height: '100%',
minHeight: 0,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
>
<div style={{ height: '62%', minHeight: 140, overflow: 'hidden' }}>
<CodeEditor
code={sim.code}
onChange={sim.setCode}
onRunToggle={sim.isRunning ? sim.stop : sim.start}
/>
</div>
<div style={{ height: '38%', minHeight: 128, overflow: 'hidden', flexShrink: 0 }}>
<SerialMonitor
logs={sim.serialLogs}
onClear={sim.clearSerialLogs}
onSendInput={sim.writeSerialInput}
/>
</div>
</div>
</div>
);
}Untuk mode belajar, render juga LessonGuide dan biarkan learningMode default true.
CircuitCanvas wajib menerima onSelectWireColor bersama activeWireColor — palet warna kabel ada di pojok kiri bawah kanvas.
CodeEditor dan SerialMonitor memakai height: 100% di dalam komponen. Kolom kanan harus punya tinggi tetap (100vh / flex minHeight: 0) dan cadangan tinggi untuk Serial. Jangan taruh CircuitValidator di atas Serial tanpa area scroll: daftar hasil Cek memanjang dan akan mendorong Serial keluar layar. Pola yang aman: misi + validator di flex: 1; overflow: auto, editor dan Serial flexShrink: 0 dengan persen tinggi seperti di atas. Contoh lengkap: apps/studio/src/pages/CmsSamplePage.tsx.
7. Cara D — Engine tanpa React
Cocok untuk Node, tes, atau UI yang Anda tulis dari nol.
import {
ArduinoRuntime,
solveCircuitNetlist,
applyNetlistToCircuitState,
type CircuitState,
} from '@dev-1o/simulator';
const circuit: CircuitState = {
components: [
{
id: 'uno-1',
type: 'wokwi-arduino-uno',
name: 'Arduino Uno R3',
x: 40,
y: 40,
rotation: 0,
properties: {},
},
],
wires: [],
};
const runtime = new ArduinoRuntime({
onPinChange: (pins) => {
const net = solveCircuitNetlist(circuit, pins, runtime.getPinModes());
circuit = applyNetlistToCircuitState(circuit, net);
},
onSerialOutput: (text) => {
process.stdout.write(text);
},
});
await runtime.start(`
void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600);
Serial.println("halo");
}
void loop() {
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
}
`);API runtime yang sering dipakai:
| Method | Fungsi |
| --- | --- |
| start(code) | Transpile sketch C++ lalu jalankan setup/loop |
| stop() | Hentikan loop dan reset pin |
| pause() | Jeda / lanjut |
| getPins() | Status tegangan pin Arduino |
| getPinModes() | INPUT / OUTPUT / INPUT_PULLUP |
| writeSerialInput(text) | Kirim teks seolah dari Serial Monitor |
| getPaused() | Apakah sedang jeda |
Engine tidak menyentuh window saat diimpor di Node. Audio buzzer hanya aktif di browser.
8. Props Studio / useSimulator
Keduanya menerima opsi yang sama (UseSimulatorOptions).
| Prop | Tipe | Default | Arti |
| --- | --- | --- | --- |
| learningMode | boolean | true | true = panel praktikum. false = sandbox. |
| initialLesson | Lesson | LESSONS[0] | Modul awal jika mode belajar. |
| initialCircuit | CircuitState | rangkaian modul awal | Isi kanvas pertama kali. |
| initialCode | string | sketch modul awal | Isi editor pertama kali. |
Opsi hanya dibaca saat mount. Mengubah prop kemudian tidak mereset simulator. Untuk memuat rangkaian baru setelah render, panggil sim.loadProject(circuit, code) atau sim.selectLesson(lesson).
Impor daftar modul:
import { LESSONS } from '@dev-1o/simulator';
<Studio initialLesson={LESSONS[2]} />9. Hasil useSimulator()
Objek yang dikembalikan (ringkasan):
Tampilan & mode
isLearningMode/setIsLearningModecurrentLesson/selectLesson/resetStarter/applySolutionisGuideCollapsed/setIsGuideCollapsed
Rangkaian
circuit—{ components, wires }addComponent(type)updateComponentPosition(id, x, y)rotateComponent(id)— +90°deleteComponent(id)updateComponentProperty(id, key, value)addWire(...)/deleteWire(id)/clearCanvas()canAddComponent(type)— di mode belajar, menolak duplikat starter
Seleksi & kabel
selectedComponentId/setSelectedComponentIdselectedWireId/setSelectedWireIdactiveWireColor/setActiveWireColor— warna kabel baru; jika ada kabel terpilih, warnanya ikut diubaheditingComponent/setEditingComponent
Palet warna kabel (WIRE_COLORS) dirender di kanvas (CircuitCanvas), bukan di header. Saat menarik kabel, palet juga muncul di banner atas.
Simulasi
isRunning/isPausedstart()/pause()/stop()
Kode & serial
code/setCodeserialLogs/clearSerialLogs/writeSerialInput(text)showEditor/setShowEditorshowSerial/setShowSerial
Proyek
loadProject(circuit, code?)isExportModalOpen/setIsExportModalOpen
Log serial dipotong otomatis di 64 KB agar halaman yang dibiarkan menyala lama tidak kehabisan memori.
10. Komponen React yang diekspor
import {
Studio,
useSimulator,
BrandMark,
Header,
Toolbox,
CircuitCanvas,
WireColorPalette,
CodeEditor,
DEFAULT_CODE_TEMPLATES,
SerialMonitor,
LessonGuide,
CircuitValidator,
ComponentPropsModal,
ShareExportModal,
WokwiWrapper,
} from '@dev-1o/simulator/react';| Komponen | Peran |
| --- | --- |
| Studio | Workspace utuh |
| Header | Mulai / jeda / stop, mode Belajar/Sandbox, ekspor, toggle panel |
| Toolbox | Katalog komponen |
| CircuitCanvas | Kanvas 2D, zoom, pan, tarik kabel, palet warna kabel |
| WireColorPalette | Swatch 8 warna kabel (sudah tertanam di CircuitCanvas; bisa dipakai ulang di layout custom) |
| CodeEditor | Editor sketch.ino (Ctrl+Enter = mulai/stop). Prop templates: objek label→kode, atau false / {} untuk menyembunyikan dropdown. Default sandbox: DEFAULT_CODE_TEMPLATES. |
| SerialMonitor | Keluaran Serial.print + kirim input |
| LessonGuide | 24 modul praktikum (LESSONS) |
| CircuitValidator | Cek rangkaian vs misi |
| ComponentPropsModal | Warna LED, nilai resistor, dsb. |
| ShareExportModal | Unduh / unggah JSON proyek |
| BrandMark | Logo logo-devio.svg plus teks Dev.io |
| WokwiWrapper | Satu custom element Wokwi |
11. Tipe data rangkaian
import type {
CircuitState,
PlacedComponent,
Wire,
ComponentType,
} from '@dev-1o/simulator';const circuit: CircuitState = {
components: [
{
id: 'uno-1',
type: 'wokwi-arduino-uno',
name: 'Arduino Uno R3',
x: 80,
y: 60,
rotation: 0, // 0 | 90 | 180 | 270
properties: {},
},
{
id: 'led-1',
type: 'wokwi-led',
name: 'LED',
x: 400,
y: 80,
rotation: 0,
properties: { color: 'red', value: false },
},
],
wires: [
{
id: 'wire-1',
fromComponentId: 'uno-1',
fromPinId: '13',
toComponentId: 'led-1',
toPinId: 'A',
color: '#ef4444',
},
],
};type harus salah satu nilai di daftar komponen. fromPinId / toPinId harus id pin yang ada di komponen itu (contoh LED: A anoda, C katoda).
Warna kabel memakai konstanta WIRE_COLORS (merah +5V, hitam GND, biru sinyal, dll.) dari @dev-1o/simulator.
12. Komponen yang tersedia
46 tipe (nilai ComponentType), termasuk Arduino Uno/Nano/Mega, ESP32 DevKit, LED, sensor, display OLED/LCD, servo, stepper, keypad, NeoPixel, dan lainnya.
Daftar lengkap label toolbox:
import { getFlatComponentCatalog } from '@dev-1o/simulator';
getFlatComponentCatalog().map((c) => [c.type, c.label]);Metadata pin, ukuran, dan properti default ada di COMPONENT_REGISTRY:
import { COMPONENT_REGISTRY } from '@dev-1o/simulator';
COMPONENT_REGISTRY['wokwi-led'].pins;
// [{ id: 'A', name: 'Anoda (A)', ... }, { id: 'C', name: 'Katoda (C)', ... }]13. Impor / ekspor proyek JSON
UI ShareExportModal sudah menangani unduh dan unggah. Jika Anda ingin file sendiri:
import {
parseProjectFile,
ProjectFileError,
APP_EXPORT_PLATFORM,
} from '@dev-1o/simulator';
try {
const project = parseProjectFile(jsonText);
sim.loadProject(project.circuit, project.code);
} catch (err) {
if (err instanceof ProjectFileError) {
alert(err.message); // pesan berbahasa Indonesia
}
}Batas: maksimal 200 komponen, 400 kabel. Tipe atau pin yang tidak dikenal ditolak.
Nilai platform pada file ekspor: Dev.io-Learning-Platform (APP_EXPORT_PLATFORM).
14. CSS dan tabrakan gaya
Impor sekali:
import '@dev-1o/simulator/style.css';Style paket sengaja diisolasi:
- token:
--devio-accent,--devio-border, … - kelas sendiri:
devio-btn,devio-seg,devio-canvas-grid, … - utility Tailwind memakai prefiks
devio:(devio:flex,devio:bg-white, …)
Stylesheet host (Tailwind v3/v4, Bootstrap, dsb.) tidak saling menimpa kelas flex mentah.
Paket tidak mengunduh font. Host boleh memakai font sendiri. Studio Dev.io memakai Plus Jakarta Sans + JetBrains Mono, tetapi itu milik aplikasi, bukan syarat paket.
15. Entry map
| Import | Isi | Butuh React? |
| --- | --- | --- |
| @dev-1o/simulator | Engine, tipe, LESSONS, COMPONENT_REGISTRY, parseProjectFile | Tidak |
| @dev-1o/simulator/react | Studio, useSimulator, komponen UI | Ya |
| @dev-1o/simulator/elements | registerElements() + auto-register di browser | Tidak, tapi butuh @wokwi/elements |
| @dev-1o/simulator/style.css | CSS terisolasi | Tidak |
import { registerElements } from '@dev-1o/simulator/elements';
await registerElements(); // aman dipanggil berkali-kali; no-op di NodeMengimpor @dev-1o/simulator/elements di browser sudah memanggil registrasi. Pemanggilan manual berguna jika Anda menunda load.
16. Yang belum didukung (v0.1)
- Kompilasi C++ di browser (tidak ada
avr-gcc/ WASM toolchain) - Mode akurat
avr8js/ unggah firmware HEX - Web component
<dev-io-studio>tanpa React - Breadboard fisik di kanvas
- Simulasi pin/board non-AVR (ESP32: mapping GPIO di netlist/visual; runtime sketch tetap berorientasi AVR)
Sketch dijalankan lewat transpiler C++ → JavaScript, bukan machine code. Cukup untuk digitalWrite, analogRead, Serial, delay, Servo, LCD, DHT, dan pola sketch praktikum. Perilaku timing mikrokontroler asli tidak dijamin identik.
17. Keamanan
Sketch pengguna dijalankan di origin halaman yang sama lewat new Function().
Ini setara mengeksekusi JavaScript arbitrary di situs Anda. Untuk produk publik / multi-pengguna:
- jalankan simulator di iframe terisolasi (origin berbeda), atau
- batasi siapa yang boleh menekan Mulai, atau
- sandbox di Web Worker / CSP yang ketat (catatan:
new Functionbutuh CSP yang mengizinkan evaluasi)
Situs dengan CSP script-src tanpa 'unsafe-eval' akan memblokir runtime transpiler.
18. Autocomplete TypeScript
Paket menyertakan .d.ts. Setelah npm install, editor akan menawarkan:
- nama fungsi (
solveCircuitNetlist,useSimulator, …) - props
Studio - field
UseSimulatorResult - union
ComponentType
Tidak perlu menyalin source Dev.io ke project Anda.
19. Contoh Vite + React
npm create vite@latest my-lab -- --template react-ts
cd my-lab
npm install
npm install @dev-1o/simulator lucide-react @wokwi/elementssrc/main.tsx:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import '@dev-1o/simulator/style.css';
import '@dev-1o/simulator/elements';
import { Studio } from '@dev-1o/simulator/react';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Studio learningMode={false} initialCircuit={{ components: [], wires: [] }} />
</StrictMode>
);npm run devBuka URL Vite (biasanya http://localhost:5173). Kanvas sandbox kosong siap dipakai.
Di monorepo Dev.io ada halaman sampel yang meniru CMS (satu modul, katalog komponen dibatasi, user merakit kabel):
http://localhost:5173/contoh-cms
Kode: apps/studio/src/pages/CmsSamplePage.tsx dan payload apps/studio/src/cms/sampleModule.ts. Halaman itu bukan entry npm; salin polanya ke app Anda. <Studio /> dari paket sudah memisahkan panel Praktikum dari editor/Serial, jadi hasil Cek tidak menendang Serial. Modul CMS memakai templates={false} pada CodeEditor agar dropdown sketch bawaan (Blink, LCD, …) tidak muncul.
20. Embed iframe & katalog CMS (JSON)
Katalog modul (GET statis)
Daftar modul tersedia di:
GET /cms/modules/index.jsonContoh respons:
{
"modules": [
{ "slug": "led-blink", "title": "LED Blink", "difficulty": "Pemula", "summary": "…" }
]
}Detail satu modul:
GET /cms/modules/{slug}.jsonField JSON mengikuti CmsPracticumModule + validatorKey (validasi diimplementasikan di app host, bukan di JSON).
Halaman embed
Sematkan simulator tanpa chrome penuh lewat iframe:
<iframe
src="https://lab.example.com/embed/led-blink"
width="100%"
height="720"
style="border:0"
allow="clipboard-write"
></iframe>Route contoh di app studio: /embed/:slug → EmbedPage (tanpa navigasi landing, templates={false}).
postMessage ke parent window
Saat dimuat di iframe, halaman embed mengirim:
| Event | Payload |
| --- | --- |
| dev-1o-simulator:ready | { type, slug } |
| dev-1o-simulator:validated | { type, slug, success, passed, total, checks[] } |
Contoh listener di LMS:
window.addEventListener('message', (event) => {
if (event.data?.type === 'dev-1o-simulator:validated') {
console.log('Lulus?', event.data.success, event.data.passed, '/', event.data.total);
}
});Validasi otomatis dikirim ulang setiap rangkaian atau kode berubah.
npm vs JSON
| Kebutuhan | Pakai |
| --- | --- |
| Modul bawaan (24 praktikum) | LESSONS dari @dev-1o/simulator |
| Modul kustom dari CMS/LMS | JSON publik + validator di app Anda |
| Sandbox bebas | <Studio learningMode={false} /> |
21. Lisensi
MIT.
Gambar komponen disediakan oleh @wokwi/elements (MIT). Cantumkan atribusi itu jika Anda mendistribusikan UI yang menampilkan elemen tersebut.
