-
Notifications
You must be signed in to change notification settings - Fork 0
Customizing
This guide serves as a foundation for creating new components for the simulator.
A component is responsible for interacting with the simulator (Arduino) and controlling a device through a driver (logic) and its visual aspect.
Generally, 2 to 3 classes are needed to implement a Component:
- A class for visual logic (UI)
- A class for circuit logic (driver)
- And a class that is our component that connects both to the simulation / virtual Arduino.
Another way to visualize it would be:
- UI is the physical element you see and interact with.
- Driver is the logic that the chip implements or internal hardware behavior.
- Component is the circuit board and wires that connect everything together.
Let's start with a very simple example, which doesn't involve any advanced logic (or driver), nor even the creation of visual elements since we'll use existing elements from @wokwi/wokwi-elements.
Let's create a component to add a 7-Segment Display and use the visual component https://elements.wokwi.com/?path=/story/7-segment--red-4.
Before implementing, it's important to understand how components (PhysicalComponent) work:
-
Constructor: The PhysicalComponent receives two parameters:
-
portMapping
: Mapping of component local pins to Arduino pins -
ui
: The HTML/Wokwi element that represents the visual part
-
-
Required methods: Every component must implement:
-
static getPorts()
: Defines which pins the component has -
static getAvailableAttrs()
: Defines which attributes are configurable -
static getInfo()
: Returns component information (name, icon, etc.) -
setupPortMapping()
: Configures how the component connects to the Arduino -
setupAttributeListeners()
: Configures listeners for attribute changes -
reset()
: Resets the component state
-
First, let's define the ports (pins) that our display will use:
import { PhysicalComponent } from './core/PhysicalComponent';
import { PortInfo, PortType } from '@/devices/PortProvider';
import { AttrInfo, AttrType } from './core/AttrInfo';
import { SevenSegmentElement } from '@wokwi/elements';
import { ArduinoUno } from '@/devices/arduino/ArduinoUno';
import { AVRRunner } from '@/devices/arduino/ATmega328Runner';
import _DISPLAY_ICON from 'url:/src/assets/components/display-7seg.svg';
// Defining the 7-segment display ports
const SEGMENT_A_PORT: PortInfo = { index: 0, name: 'A', description: 'Segment A', type: PortType.DIGITAL };
const SEGMENT_B_PORT: PortInfo = { index: 1, name: 'B', description: 'Segment B', type: PortType.DIGITAL };
const SEGMENT_C_PORT: PortInfo = { index: 2, name: 'C', description: 'Segment C', type: PortType.DIGITAL };
const SEGMENT_D_PORT: PortInfo = { index: 3, name: 'D', description: 'Segment D', type: PortType.DIGITAL };
const SEGMENT_E_PORT: PortInfo = { index: 4, name: 'E', description: 'Segment E', type: PortType.DIGITAL };
const SEGMENT_F_PORT: PortInfo = { index: 5, name: 'F', description: 'Segment F', type: PortType.DIGITAL };
const SEGMENT_G_PORT: PortInfo = { index: 6, name: 'G', description: 'Segment G', type: PortType.DIGITAL };
const DP_PORT: PortInfo = { index: 7, name: 'DP', description: 'Decimal Point', type: PortType.DIGITAL };
// Defining configurable attributes
const DISPLAY_ATTRS: AttrInfo[] = [
{
name: 'color',
description: 'Display color',
type: AttrType.COLOR,
defaultValue: '#ff0000'
}
];
export class Display7SegComponent extends PhysicalComponent {
// Required static methods
static getPorts(): PortInfo[] {
return [
SEGMENT_A_PORT, SEGMENT_B_PORT, SEGMENT_C_PORT, SEGMENT_D_PORT,
SEGMENT_E_PORT, SEGMENT_F_PORT, SEGMENT_G_PORT, DP_PORT
];
}
static getAvailableAttrs(): AttrInfo[] {
return DISPLAY_ATTRS;
}
/**
* Get complete component information
*/
static getInfo() {
return {
name: 'Display 7-Seg',
description: 'Seven segment display for showing digits',
icon: _DISPLAY_ICON,
uiClass: SevenSegmentElement
};
}
}
Important concepts:
- PortInfo: Defines a component pin with index, name, description and type
- AttrInfo: Defines a configurable attribute with type, default value and validation
- getInfo(): Returns component metadata for the system
- AttrType.COLOR: Special type for color selection
export class Display7SegComponent extends PhysicalComponent {
// ... previous static methods ...
// The UI element is received by the constructor
private get displayElement(): SevenSegmentElement {
return this.ui as SevenSegmentElement;
}
protected setupPortMapping(arduino: ArduinoUno, runner: AVRRunner): void {
// Configure listeners for Arduino pin changes
// Each port connects to a specific segment
this.onPortChange(SEGMENT_A_PORT, (value) => this.updateSegment(0, value));
this.onPortChange(SEGMENT_B_PORT, (value) => this.updateSegment(1, value));
this.onPortChange(SEGMENT_C_PORT, (value) => this.updateSegment(2, value));
this.onPortChange(SEGMENT_D_PORT, (value) => this.updateSegment(3, value));
this.onPortChange(SEGMENT_E_PORT, (value) => this.updateSegment(4, value));
this.onPortChange(SEGMENT_F_PORT, (value) => this.updateSegment(5, value));
this.onPortChange(SEGMENT_G_PORT, (value) => this.updateSegment(6, value));
this.onPortChange(DP_PORT, (value) => this.updateSegment(7, value));
}
private updateSegment(index: number, value: boolean): void {
// Get current display values
const currentValues = this.displayElement.values || [];
// Update specific segment
currentValues[index] = value ? 1 : 0;
// Apply new values to visual element
this.displayElement.values = [...currentValues];
}
protected setupAttributeListeners(): void {
// Configure listeners for attribute changes and synchronize with visual element
this.onAttrChange('color', (value: string) => {
this.displayElement.color = value;
});
}
reset(): void {
// Reset display to initial state
this.displayElement.values = [0, 0, 0, 0, 0, 0, 0, 0];
}
}
Important concepts:
- setupPortMapping(): Connects Arduino pins to component callbacks
- onPortChange(): Registers a callback that is called when a pin changes state
- setupAttributeListeners(): Configures synchronization between attributes and visual element
- this.ui: Reference to the visual element (SevenSegmentElement) passed in constructor
For the component to appear in the component panel, you must add it to the componentClasses
array in the ComponentController.ts
file:
// In src/app/simulation/ComponentController.ts
// Add to componentClasses array
private static componentClasses: ComponentDefinition[] = [
ILI9341Component,
// ...
SDCardComponent,
Display7SegComponent // ← Add here
];
// Example: Direct segment control
void setup() {
// Configure pins as output
for (int pin = 2; pin <= 9; pin++) {
pinMode(pin, OUTPUT);
}
}
void loop() {
// Show number 0
digitalWrite(2, HIGH); // Segmento A
digitalWrite(3, HIGH); // Segmento B
digitalWrite(4, HIGH); // Segmento C
digitalWrite(5, HIGH); // Segmento D
digitalWrite(6, HIGH); // Segmento E
digitalWrite(7, HIGH); // Segmento F
digitalWrite(8, LOW); // Segmento G
digitalWrite(9, LOW); // Ponto decimal
delay(1000);
// Show number 8 (all segments)
for (int pin = 2; pin <= 8; pin++) {
digitalWrite(pin, HIGH);
}
delay(1000);
// Turn off all segments
for (int pin = 2; pin <= 9; pin++) {
digitalWrite(pin, LOW);
}
delay(1000);
}
- Component Structure: How to extend PhysicalComponent
- Port Definition: How to create PortInfo for each pin
- Configurable Attributes: How to add visual properties to the component
- getInfo() Method: How to provide component metadata
- Pin Listeners: How to react to Arduino pin changes
- Visual Synchronization: How to update UI element based on signals
This tutorial covered the basic concepts. In the next tutorial we will see:
- Driver Creation: Implementation of more complex logic (like BCD decoding)
- Serial Communication: Components that use I2C, SPI, UART
- Custom Visual Elements: Creation of custom visual elements