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.