Skip to content

Sensor Architecture

The sensor framework is built around a double-dispatch system that allows type-safe, extensible sensor implementations across multiple Modbus backends.

Why Tag-Based + Backend Double Dispatch?

This design allows you to: - Add new measurement types without modifying the Sensor base class - Support multiple Modbus backends (minimalmodbus, pymodbus, etc.) without changing sensor logic - Ensure type-safe tag validation at the sensor level - Keep sensor-specific and backend-specific read logic isolated and testable - Share the same tag types across different sensor models AND backends (e.g., CO2Tag works with any CO2 sensor using any supported Modbus backend)

Core Components

classDiagram
    %% Backend abstraction
    class ModbusBackendTag {
        <<abstract>>
        +identify backend type
    }
    class MinimalmodbusBackendTag {
    }

    %% Core classes
    class Sensor {
        -instrument: Instrument
        -backend_tag: ModbusBackendTag
        -_valid_tags: List~ReadTag~
        -_namespace: dict
        +read(tags) dict~ReadTag, Any~
        +write(settings) None
        +get_valid_tags() List~ReadTag~
    }

    class ReadTag {
        <<abstract>>
        +metadata: ReadTagMetadata
    }

    class sensor_namespace {
        <<per-sensor>>
        +_read(instrument, tag, backend_tag)
        +_write(instrument, tag, value, backend_tag)
    }

    class modbusbackend_namespace {
        <<global>>
        +_read_float()
        +_write_register()
    }

    %% Relationships
    ModbusBackendTag <|-- MinimalmodbusBackendTag

    Sensor o-- ModbusBackendTag : owns
    Sensor o-- ReadTag : validates and owns
    Sensor o-- sensor_namespace : owns
    Sensor ..> sensor_namespace : dispatches to
    sensor_namespace ..> modbusbackend_namespace : dispatches to

    sensor_namespace ..> ModbusBackendTag : dispatches on
    Sensor ..> ReadTag : dispatches on
    ReadTag ..> sensor_namespace : selects implementation

How It Works

The architecture uses a two-level dispatch system:

1. Initialization

When creating a sensor, the Sensor class uses the global modbusbackend_namespace to create and configure the instrument: - _create_instrument() dispatches on the backend_tag type to instantiate the appropriate Modbus client - _setup_instrument() dispatches on the instrument type to configure serial communication parameters

2. Reading/Writing

When calling sensor.read([CO2, TEMPERATURE]): 1. The Sensor validates that each tag is in its _valid_tags list 2. For each tag, retrieves the _read dispatcher from its sensor-specific namespace 3. Calls _read(self.instrument, tag, self.backend_tag) which performs double dispatch: - First dispatch on tag type (e.g., CO2Tag vs TemperatureTag) - determines WHAT to read - Second dispatch on backend tag type (e.g., MinimalmodbusBackendTag vs PymodbusBackendTag) - determines HOW to read 4. The appropriate sensor-specific, backend-specific implementation executes

3. Namespace Isolation

  • Global modbusbackend_namespace: Shared across all sensors for backend lifecycle management
  • Per-sensor namespaces: Each sensor (GMP252, GMM222, etc.) has its own _read and _write dispatchers to avoid conflicts

This design decouples sensor logic (register addresses, data decoding) from backend logic (communication protocol), allowing any sensor to work with any backend through proper registration. The tag acts as a measurement type selector, while the backend tag acts as a backend implementation selector.