Skip to content

Customizing

Ricardo JL Rufino edited this page Jul 29, 2025 · 1 revision

Creating Components for the Simulator

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.

Diagram:

Architecture

Let's Go

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.

Step 1: Understanding the Architecture

Before implementing, it's important to understand how components (PhysicalComponent) work:

  1. 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
  2. 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

Step 2: Defining Ports and Attributes

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

Step 3: Implementing Component Logic

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

Step 4: Registration in ComponentController

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
];

Step 5: Arduino Usage Example

// 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);
}

Concepts Learned

  1. Component Structure: How to extend PhysicalComponent
  2. Port Definition: How to create PortInfo for each pin
  3. Configurable Attributes: How to add visual properties to the component
  4. getInfo() Method: How to provide component metadata
  5. Pin Listeners: How to react to Arduino pin changes
  6. Visual Synchronization: How to update UI element based on signals

Next Steps

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
Clone this wiki locally