Skip to content

Device Classes

This page documents the mock device helper classes that make it easier to simulate hardware devices.

I2CDevice

I2CDevice(addr, i2c)

A single I2C device added to a mock_machine.I2C bus.

This is a utility class for simulating a real device, not representative of a "real" micropython machine class.

Source code in mock_machine.py
def __init__(self, addr, i2c):
    self.addr = addr

    # Dict of buffers used for addressed operations
    self.register_values = {}

    # A buffer used for non-addressed reads
    self.readbuf = bytes()

    # Add self to I2C
    i2c.add_device(self)

Functions

readfrom(nbytes, stop=True)

Read nbytes from the peripheral.

Returns a bytes object with the data read.

Source code in mock_machine.py
def readfrom(self, nbytes, stop=True):
    """
    Read nbytes from the peripheral.

    Returns a bytes object with the data read.
    """
    if len(self.readbuf) < nbytes:
        raise ValueError(f"Insufficient bytes to read nbytes={nbytes} with readfrom()")
    buf = self.readbuf[:nbytes]
    return buf

readfrom_into(buf, stop=True)

Read into buf from the peripheral.

The number of bytes read will be the length of buf.

The method returns None.

Source code in mock_machine.py
def readfrom_into(self, buf, stop=True):
    """
    Read into buf from the peripheral.

    The number of bytes read will be the length of buf.

    The method returns None.
    """
    if len(self.readbuf) < len(buf):
        raise ValueError(f"Insufficient bytes to read {len(buf)} with readfrom_into()")
    buf[:] = self.readbuf[: len(buf)]

writeto(buf, stop=True) staticmethod

Write the bytes from buf to the peripheral.

If a NACK is received following the write of a byte from buf then the remaining bytes are not sent.

If writeto is required for a peripheral, then this function will need to be overridden with desired functionality. Otherwise this function defaults to ACK for all bytes.

The function returns the number of ACKs that were received.

Source code in mock_machine.py
@staticmethod
def writeto(buf, stop=True):
    """
    Write the bytes from buf to the peripheral.

    If a NACK is received following the write of a byte from buf then the remaining bytes are
    not sent.

    If `writeto` is required for a peripheral, then this function will need to be overridden
    with desired functionality. Otherwise this function defaults to ACK for all bytes.

    The function returns the number of ACKs that were received.
    """
    return len(buf)

readfrom_mem(memaddr, nbytes)

Read nbytes from the peripheral starting at memaddr.

Returns a bytes object with the data read.

Source code in mock_machine.py
def readfrom_mem(self, memaddr, nbytes):
    """
    Read nbytes from the peripheral starting at memaddr.

    Returns a bytes object with the data read.
    """
    if memaddr not in self.register_values:
        raise IndexError(
            f"Unknown memory address memaddr={memaddr}",
        )
    if len(self.register_values[memaddr]) < nbytes:
        raise ValueError(f"Insufficient bytes to read nbytes={nbytes} from memaddr={memaddr}")
    buf = self.register_values[memaddr][:nbytes]
    return buf

readfrom_mem_into(memaddr, buf)

Read into buf from the peripheral specified by addr, starting at memaddr.

The number of bytes read is the length of buf. The argument addrsize specifies the address size in bits.

The method returns None.

Source code in mock_machine.py
def readfrom_mem_into(self, memaddr, buf):
    """
    Read into buf from the peripheral specified by addr, starting at memaddr.

    The number of bytes read is the length of buf. The argument addrsize specifies the address
    size in bits.

    The method returns None.
    """
    if memaddr not in self.register_values:
        raise IndexError(
            f"Unknown memory address memaddr=0x{memaddr:x}",
        )
    if len(self.register_values[memaddr]) < len(buf):
        raise ValueError(
            f"Insufficient bytes to read len(buf)={len(buf)} from memaddr={memaddr}"
        )
    buf[:] = self.register_values[memaddr][: len(buf)]

writeto_mem(memaddr, buf)

Write buf to the peripheral specified by addr, starting at memaddr.

The number of bytes written is the length of buf. The argument addrsize specifies the address size in bits.

The method returns None.

Source code in mock_machine.py
def writeto_mem(self, memaddr, buf):
    """
    Write buf to the peripheral specified by addr, starting at memaddr.

    The number of bytes written is the length of buf. The argument addrsize specifies the
    address size in bits.

    The method returns None.
    """
    self.register_values[memaddr] = buf

The I2CDevice class is a helper for simulating I2C devices. It automatically registers itself with the I2C bus when created.

Basic Usage

from mock_machine import I2C, I2CDevice

# Create bus and device
i2c = I2C(0)
device = I2CDevice(addr=0x68, i2c=i2c)

# Set register values
device.register_values[0x75] = b'\x68'  # WHO_AM_I register

# Device is now accessible via I2C
data = i2c.readfrom_mem(0x68, 0x75, 1)
assert data == b'\x68'

Extending I2CDevice

Create custom device simulations by extending I2CDevice:

class MockSensor(I2CDevice):
    def __init__(self, addr, i2c):
        super().__init__(addr, i2c)
        # Initialize with default values
        self.temperature = 25.0
        self.register_values[0x00] = self._encode_temp()

    def _encode_temp(self):
        # Convert temperature to bytes
        raw = int(self.temperature * 100)
        return bytes([raw >> 8, raw & 0xFF])

    def writeto_mem(self, memaddr, buf):
        if memaddr == 0x01:  # Config register
            # Handle configuration changes
            self.config = buf[0]
        super().writeto_mem(memaddr, buf)

    def set_temperature(self, temp):
        self.temperature = temp
        self.register_values[0x00] = self._encode_temp()

Memory Class

Memory(data)

https://docs.micropython.org/en/latest/library/machine.html#memory-access

Source code in mock_machine.py
def __init__(self, data):
    self.data = data

Functions

__getitem__(idx)

Source code in mock_machine.py
def __getitem__(self, idx):
    return self.data

The Memory class provides memory-mapped I/O simulation for mem8, mem16, and mem32.

Usage

# Memory objects are pre-created
from mock_machine import mem8, mem16, mem32

# Read memory (returns fixed values)
byte_val = mem8[0x1000]    # Returns 0xFF
word_val = mem16[0x1000]   # Returns 0xFFFF
dword_val = mem32[0x1000]  # Returns 0xFFFFFFFF

Creating Custom Mock Devices

Best Practices

  1. Inherit from Base Classes: Extend I2CDevice for I2C devices
  2. Initialize Registers: Set default register values in __init__
  3. Override Methods: Customize behavior by overriding read/write methods
  4. Document Behavior: Clearly document what your mock simulates

Example: Mock RTC Device

import struct
import time

class MockRTC(I2CDevice):
    """Mock DS3231 RTC device."""

    def __init__(self, addr=0x68, i2c=None):
        super().__init__(addr, i2c)
        self.time_offset = 0

    def readfrom_mem(self, memaddr, nbytes):
        if memaddr == 0x00 and nbytes >= 7:
            # Return current time as BCD
            t = time.localtime(time.time() + self.time_offset)
            data = bytearray([
                self._to_bcd(t[5]),      # Seconds
                self._to_bcd(t[4]),      # Minutes
                self._to_bcd(t[3]),      # Hours
                self._to_bcd(t[6] + 1),  # Day of week
                self._to_bcd(t[2]),      # Date
                self._to_bcd(t[1]),      # Month
                self._to_bcd(t[0] % 100) # Year
            ])
            return bytes(data[:nbytes])
        return super().readfrom_mem(memaddr, nbytes)

    def writeto_mem(self, memaddr, buf):
        if memaddr == 0x00 and len(buf) >= 7:
            # Set time from BCD values
            # (Implementation would decode BCD and set time_offset)
            pass
        super().writeto_mem(memaddr, buf)

    @staticmethod
    def _to_bcd(val):
        """Convert value to BCD format."""
        return ((val // 10) << 4) | (val % 10)

Example: Mock SPI Flash

class MockSPIFlash:
    """Mock SPI flash memory device."""

    def __init__(self, spi, cs_pin, size=1024*1024):
        self.spi = spi
        self.cs = cs_pin
        self.size = size
        self.memory = bytearray(size)
        self.commands = {
            0x9F: self._jedec_id,
            0x03: self._read_data,
            0x02: self._page_program,
            0x20: self._sector_erase,
        }

    def _jedec_id(self, data):
        """Return JEDEC ID."""
        return b'\xEF\x40\x14'  # Example: W25Q80

    def _read_data(self, data):
        """Read data from address."""
        if len(data) >= 3:
            addr = (data[0] << 16) | (data[1] << 8) | data[2]
            # Return data from address
            # (Implementation would handle read)
        return b''

    def execute_command(self, cmd_data):
        """Execute SPI command."""
        if cmd_data[0] in self.commands:
            return self.commands[cmd_data[0]](cmd_data)
        return b''