Skip to main content

Custom Sensors

Sensor Builder

Configure your sensor below and the code is generated automatically. Copy it into your project and fill in the TODO sections with your hardware logic.

Class: MySensor | Type: my_sensor
my_sensor.py
# client/modules/sensors/my_sensor.py
from .base import BaseSensor


class MySensor(BaseSensor):
    name = "My Sensor"
    sensor_type = "my_sensor"
    default_gpio = 18
    module = "XY-100"
    description = "TODO: describe what this sensor does"
    icon = "zap"
    wiring = (
        {"pin": "VCC", "connect": "3V3 [pin 17]"},
        {"pin": "GND", "connect": "GND [pin 14]"},
        {"pin": "DO", "connect": "GPIO 18 [pin ??]"},
    )

    def __init__(self, gpio=None, **kwargs):
        super().__init__(gpio, **kwargs)

    def start(self, on_trigger, on_release):
        self._on_trigger = on_trigger
        self._on_release = on_release
        self._running = True
        # TODO: set up GPIO monitoring here

    def stop(self):
        self._running = False
        # TODO: clean up GPIO resources

    def read_value(self):
        return self._read_gpio(pull_up=True)
__init__.py registration
# Add to client/modules/sensors/__init__.py
from .my_sensor import MySensor

SENSOR_REGISTRY: dict[str, type] = {
    # ... existing sensors ...
    "my_sensor": MySensor,
}

Step by step

The sensor system is modular. To add a new sensor type:

  1. Create a new file in client/modules/sensors/ (e.g. my_sensor.py)
  2. Subclass BaseSensor from client/modules/sensors/base.py
  3. Implement start(on_trigger, on_release) and stop()
  4. Set the class attributes for the dashboard UI
  5. Register the class in client/modules/sensors/__init__.py

Class attributes

AttributeTypeRequiredDescription
namestrYesHuman-readable name shown in the dashboard
sensor_typestrYesShort identifier used in settings and API (e.g. "my_sensor")
default_gpiointYesDefault BCM GPIO pin number
modulestrNoHardware module number (e.g. "KY-025")
descriptionstrNoOne-line description shown in sensor details
use_casestrNoWhat the sensor is good for
iconstrNoIcon key for the dashboard ("magnet", "eye", "zap", "rotate", "hand", "gate", "circle", "wrench")
imagestrNoImage filename (without extension) in server/public/sensors/. Defaults to lowercase module name (e.g. "ky-025"). Place a .png file there to show a photo in the dashboard.
wiringtuple[dict, ...]NoWiring instructions, each dict has pin and connect keys
wiring_notestrNoExtra note shown below the wiring table
calibration_schematuple[dict, ...]NoCalibration parameters for the dashboard UI (see below)

Example

# client/modules/sensors/my_sensor.py
from .base import BaseSensor

class MySensor(BaseSensor):
name = "My Custom Sensor"
sensor_type = "my_sensor"
default_gpio = 18
module = "XY-100"
description = "Custom sensor for my specific use case"
use_case = "Monitoring a custom trigger condition"
icon = "zap"
wiring = (
{"pin": "VCC", "connect": "3V3 [pin 17]"},
{"pin": "GND", "connect": "GND [pin 14]"},
{"pin": "DO", "connect": "GPIO 18 [pin 12]"},
)
wiring_note = "Optional note about wiring specifics."

def __init__(self, gpio=None, **kwargs):
super().__init__(gpio, **kwargs)
self._device = None

def start(self, on_trigger, on_release):
# Set up your hardware monitoring here
# Call on_trigger() when the sensor fires
# Call on_release() when the sensor resets
self._on_trigger = on_trigger
self._on_release = on_release
self._running = True

def stop(self):
# Clean up hardware resources
self._running = False

def read_value(self):
# Optional: return raw GPIO state for the wiring test panel
return self._read_gpio(pull_up=True)

Adding calibration parameters

To add calibration sliders to the dashboard for your sensor, define calibration_schema. Each entry is a dict describing one slider. The values are passed as keyword arguments to your __init__:

class MySensor(BaseSensor):
# ... other attributes ...

calibration_schema = (
{
"key": "threshold", # kwarg name passed to __init__
"name": "Threshold", # label shown in dashboard
"type": "range", # always "range" for now
"min": 1,
"max": 100,
"default": 50,
"step": 1,
"unit": "seconds", # optional, shown next to the value
"description": "How sensitive the sensor is",
"labels": {"min": "Low", "max": "High"},
},
)

def __init__(self, gpio=None, **kwargs):
super().__init__(gpio, **kwargs)
self._threshold = kwargs.get("threshold", 50)

The dashboard renders the sliders automatically and persists the values in settings.json under Sensor.calibration. See calibration for how the built-in sensors use this.

Registering

Add your sensor to the registry in client/modules/sensors/__init__.py:

from .my_sensor import MySensor

SENSOR_REGISTRY: dict[str, type] = {
# ... existing sensors ...
"my_sensor": MySensor,
}

The new sensor will automatically appear in the dashboard and API.