Shopping cart

Third‑party integrations — developers’ paradise

Welcome to the smart home developers’ paradise. If you can write print('hello') in Python, you’ll feel at home here. No YAML spaghetti—just minimal, readable Python that gets real work done.

Image

Python reads clearly, works quickly, and gets out of your way. Its popularity has grown over the last few decades for good reason. We have used it since the early 2000s and still choose it for serious, clean work.

SIMO.io is built with Python and the Django framework, following Pythonic practices for structure and clarity. It is also designed to be extended in Python. Not YAML, not JSON, not JavaScript—just Python.

If you are new to programming, Python is an easy way to learn. And learning while shaping your home is honest fun.

Welcome to the developers’ paradise. If you can write print('hello'), you’re already in. This is a quick walk: small, friendly Python pieces that fall into place without fuss.

This guide gets you moving quickly. We’ll build a small app named simo_hello: start with a switch, add a dimmer and a sensor, include forms, models, and admin, then define a gateway (auto‑created), optional discovery, a tiny page, REST endpoints, and dynamic settings. A few simple ideas, and you have a complete integration.

Before we start — finding your hub

Everything here happens on a SIMO.io hub. Two easy doors stand open:

  • Django Admin from the website: go to simo.io → sign in → My Instances → pick your instance → click Admin. Hub masters get one‑click access.
  • SSH (key‑only): add your public SSH key to your user in Django Admin (Users → your account → Public SSH Key). Your Instances dashboard shows a ready‑to‑copy command like ssh -p 25422 root@l1.simo.io. On site, you can also SSH to the hub’s LAN IP. Fresh hubs ship with no keys; remove keys to revoke access.

Nice to have nearby:

  • Project dir: /etc/SIMO/hub (you’ll add your app here and see manage.py).
  • Virtualenv: workon simo-hub (activates the hub’s Python environment).
  • Supervisor: supervisorctl status all (peek), supervisorctl restart all (refresh), or restart a single service (e.g., simo-gunicorn).
  • Logs: service logs live under /var/log/simo; per‑gateway and per‑component logs are also visible in Admin.

A gentle plan

Our little app, simo_hello, touches all the right pieces you’ll reuse for any integration:

  • Controllers (per‑device logic) that reuse SIMO.io base types and app widgets (for full mobile support, stick to the built‑ins).
  • Gateway (speaks your device language) that sends commands and, if you like, polls gently.
  • Forms that save configuration to JSON; no custom storage needed.
  • Models + Admin to keep a tidy device registry.
  • Utils for protocol calls, so everything stays readable.
  • Optionally: discovery, auto_urls + views, api.py (DRF), and dynamic_settings.py.

Chapter 1 — Say hello to Django on a hub

A SIMO.io integration is a plain Django app. Your hub’s local settings live at /etc/SIMO/settings.py and start by importing the platform defaults:

"""
Django settings for SIMO.io project.
"""
import os
import sys
from simo.settings import *  # platform defaults (INSTALLED_APPS, etc.)

SECRET_KEY = '...'
DEBUG = False

BASE_DIR = '/etc/SIMO'
HUB_DIR = os.path.join(BASE_DIR, 'hub')
VAR_DIR = os.path.join(BASE_DIR, '_var')
STATIC_ROOT = os.path.join(VAR_DIR, 'static')
MEDIA_ROOT = os.path.join(VAR_DIR, 'media')
LOG_DIR = os.path.join(VAR_DIR, 'logs')

Because simo.settings is imported, all core settings are already in place, including INSTALLED_APPS. The local file holds overrides. A typical extension looks like this:

# /etc/SIMO/settings.py
INSTALLED_APPS += ['simo_hello']

The app package is a tiny Python package:

# /etc/SIMO/hub/simo_hello/__init__.py
# empty is fine — this makes it a Python package

These entries are enough for Django to discover simo_hello. supervisorctl restart all to refresh services; logs live under /var/log/simo.

Chapter 2 — The SIMO.io idea: components, controllers, gateways

Everything is a Component. A light, a sensor, a lock — each is a component with a base type (e.g., switch, dimmer, binary-sensor, rgbw-light, lock, blinds, gate). The SIMO.io app knows how to render and interact with these shapes.

Controllers add behavior to a component. They reuse SIMO.io base types and app widgets (that’s how the mobile app stays happy). A controller validates values, offers friendly helpers (like turn_on()), and translates to/from your device language.

Gateways do the talking. A controller’s send() becomes a small message to its gateway. The gateway performs the I/O (HTTP/TCP/serial/etc.). When the device speaks back, the gateway calls _receive_from_device(). The UI updates; history is saved. You don’t need to touch MQTT yourself.

Two files you’ll enjoy: controllers.py and gateways.py. They plug in automatically.

Chapter 3 — A first switch

Imagine a remote switch that speaks in strings: "ON" and "OFF". SIMO.io’s core Switch uses True/False for its actions. A small controller can sit between them and translate both ways.

The idea is simple: a controller class represents the switch locally; the _prepare_for_send() hook turns a local value into the device’s format; the _prepare_for_set() hook turns a device value back into the local format. The gateway (next) carries messages to and from the real device.

# /etc/SIMO/hub/simo_hello/controllers.py
from simo.core.controllers import Switch
from .gateways import HelloGatewayHandler


class StringSwitch(Switch):
    name = "String Switch"
    gateway_class = HelloGatewayHandler

    # Local True/False → device strings
    def _prepare_for_send(self, value):
        return 'ON' if value is True else 'OFF'

    # Device strings → local True/False
    def _prepare_for_set(self, value):
        if value == 'ON':
            return True
        if value == 'OFF':
            return False
        # A gentle fallback for odd values
        return bool(value)
 

The gateway routes values between SIMO.io and real devices. perform_value_send() is your transmit path. When a device reports back, the gateway calls the controller’s _receive_from_device(), which updates state and the UI. Here is a small demo gateway that stores values in an internal map and reflects them back every 3 seconds:

# /etc/SIMO/hub/simo_hello/gateways.py
from simo.core.gateways import BaseObjectCommandsGatewayHandler
from simo.core.forms import BaseGatewayForm
from simo.core.loggers import get_gw_logger
from simo.core.models import Component


class HelloGatewayHandler(BaseObjectCommandsGatewayHandler):
    name = "Hello Protocol"
    config_form = BaseGatewayForm
    auto_create = True
    periodic_tasks = (('catch_values', 3),)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = get_gw_logger(self.gateway_instance.id)
        self._values_on_the_go = {}

    def perform_value_send(self, component, value):
        # Replace with a real device call in practice
        self.logger.info(f"send to {component} => {value}")
        # Demo: stash the value; a periodic task will reflect it back
        self._values_on_the_go[component.id] = value

    def catch_values(self):
        self.logger.info("Catch values every 3s")
        # Example only: reflect values on the go back to the component
        for component_id, value in list(self._values_on_the_go.items()):
            comp = Component.objects.filter(pk=component_id).first()
            if comp:
                self.logger.info(f"receiving value {value} for {comp}")
                comp.controller._receive_from_device(value)
            self._values_on_the_go.pop(component_id, None)
Tip: Prefer auto_create = True on gateways so the hub creates them automatically after a restart. Use manual creation only when user input is required before a gateway can operate (for example, credentials or non‑default ports).
A simple rhythm. Controllers validate and translate. Gateways talk to devices. When the device confirms, _receive_from_device() tidies up. The UI follows along.

A Gateway entry (“Hello Protocol”) in Admin pairs with a Component (“Hello Switch”). Toggling in the SIMO.io app produces a friendly log here; a real device call in its place makes the same rhythm drive hardware.

Where to watch logs. With auto_create = True, the “Hello Protocol” gateway appears automatically after services restart. Open it in Django Admin to see a live log stream while you interact with the Hello Switch. System service logs live under /var/logs/simo on the hub.

Chapter 4 — Forms that configure components

Every controller declares the form that configures it. SIMO.io uses standard Django forms end‑to‑end: the same form powers Django Admin and the SIMO.io app. Users never touch YAML or JSON; they fill a friendly form. On save, fields outside the core Component model land in component.config as JSON.

Standard Django applies: fields, widgets, clean_* methods, clean(), and validation errors. Model choices are supported; SIMO.io stores the primary keys under config for you.

Let’s give our hello switch a required remote ID. We add a config form and attach it to the controller. The controller’s defaults ensure a predictable starting state.

# /etc/SIMO/hub/simo_hello/forms.py
from django import forms
from simo.core.forms import BaseComponentForm


class HelloSwitchConfigForm(BaseComponentForm):
    remote_id = forms.CharField(
        max_length=64,
        label="Remote ID",
        help_text="Identifier used by the remote device/protocol"
    )

    def clean_remote_id(self):
        rid = self.cleaned_data["remote_id"].strip()
        if not rid:
            raise forms.ValidationError("Remote ID is required.")
        # Add any format checks here (e.g., allowed chars)
        return rid
# /etc/SIMO/hub/simo_hello/controllers.py
from simo.core.controllers import Switch
from .gateways import HelloGatewayHandler
from .forms import HelloSwitchConfigForm


class StringSwitch(Switch):
    name = "String Switch"
    gateway_class = HelloGatewayHandler
    config_form = HelloSwitchConfigForm
    default_config = {"remote_id": ""}

    # Local True/False → device strings
    def _prepare_for_send(self, value):
        return 'ON' if value is True else 'OFF'

    # Device strings → local True/False
    def _prepare_for_set(self, value):
        if value == 'ON':
            return True
        if value == 'OFF':
            return False
        return bool(value)

Inside the gateway, read component.config to use the value in device I/O:

# /etc/SIMO/hub/simo_hello/gateways.py
from simo.core.gateways import BaseObjectCommandsGatewayHandler
from simo.core.forms import BaseGatewayForm
from simo.core.loggers import get_gw_logger
from simo.core.models import Component


class HelloGatewayHandler(BaseObjectCommandsGatewayHandler):
    name = "Hello Protocol"
    config_form = BaseGatewayForm
    auto_create = True
    periodic_tasks = (('catch_values', 3),)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = get_gw_logger(self.gateway_instance.id)
        self._values_on_the_go = {}

    def perform_value_send(self, component, value):
        remote_id = component.config.get('remote_id')
        self.logger.info(f"send to {component} (rid={remote_id}) => {value}")
        self._values_on_the_go[component.id] = value

    def catch_values(self):
        for component_id, value in list(self._values_on_the_go.items()):
            comp = Component.objects.filter(pk=component_id).first()
            if comp:
                self.logger.info(f"receiving value {value} for {comp}")
                comp.controller._receive_from_device(value)
            self._values_on_the_go.pop(component_id, None)

How forms tie into controllers

  • Attach the form on the controller: set config_form = YourForm. SIMO.io uses it in Django Admin and the SIMO.io app.
  • Where values live: fields not part of the Component model are stored under component.config as JSON. ConfigFieldsMixin takes care of mapping models to their primary keys.
  • Default config: set default_config on the controller to seed sensible defaults when adding new components.
  • Validation: use clean() or clean_* to validate and normalize input. Errors surface inline in the app.
  • Use in runtime: access self.component.config from controllers, or component.config from gateways.
Mobile first. Superuser backed. Well‑defined forms let owners add and configure components directly from the SIMO.io app. For the deeper bench, the same forms present cleanly in Django Admin. See Mobile first — Superuser backed.

Chapter 5 — Models and admin for richer integrations

Forms cover most configuration: values land in component.config and the controller/gateway read them. Some integrations, however, need durable, queryable data: discovered devices, playlists, per‑device capabilities, statistics. That is where Django models shine. Register them in Admin for visibility and control; reference them from component forms using ModelChoiceField.

When to reach for models Use a model when data is shared between multiple components, discovered by a gateway, needs indexing/search in Admin, or evolves over time outside a single component’s config.

A tiny model for hello devices

Here’s a minimal registry for remote hello devices. We track remote_id, a friendly label, and liveness. A small signal keeps component alive in sync with the model.

# /etc/SIMO/hub/simo_hello/models.py
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from simo.core.models import Component


class HelloDevice(models.Model):
    remote_id = models.CharField(max_length=64, unique=True, db_index=True)
    label = models.CharField(max_length=120)
    online = models.BooleanField(default=True)
    last_seen = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.label} ({self.remote_id})"


@receiver(post_save, sender=HelloDevice)
def update_component_online(sender, instance, created, **kwargs):
    if created:
        return
    from .gateways import HelloGatewayHandler
    for comp in Component.objects.filter(
        gateway__type=HelloGatewayHandler.uid, base_type='switch'
    ):
        if comp.config.get('hello_device') == instance.id:
            comp.alive = instance.online
            comp.save(update_fields=['alive'])

Admin: keep the registry tidy

# /etc/SIMO/hub/simo_hello/admin.py
from django.contrib import admin
from .models import HelloDevice


@admin.register(HelloDevice)
class HelloDeviceAdmin(admin.ModelAdmin):
    list_display = 'remote_id', 'label', 'online', 'last_seen'
    search_fields = 'remote_id', 'label'
    list_filter = 'online',
    readonly_fields = 'last_seen',

Model‑backed configuration

Swap the simple string field for a ModelChoiceField. The SIMO.io form serializer stores the selected object’s primary key into component.config automatically. The gateway then resolves it at runtime.

# /etc/SIMO/hub/simo_hello/forms.py
from django import forms
from simo.core.forms import BaseComponentForm
from .models import HelloDevice


class HelloSwitchModelConfigForm(BaseComponentForm):
    hello_device = forms.ModelChoiceField(
        label='Hello device', queryset=HelloDevice.objects.all()
    )
# /etc/SIMO/hub/simo_hello/controllers.py
from simo.core.controllers import Switch
from .gateways import HelloGatewayHandler
from .forms import HelloSwitchModelConfigForm


class StringSwitch(Switch):
    name = "String Switch"
    gateway_class = HelloGatewayHandler
    config_form = HelloSwitchModelConfigForm
    default_config = {"hello_device": None}
# /etc/SIMO/hub/simo_hello/gateways.py
from simo.core.gateways import BaseObjectCommandsGatewayHandler
from simo.core.loggers import get_gw_logger
from .models import HelloDevice


class HelloGatewayHandler(BaseObjectCommandsGatewayHandler):
    name = "Hello Protocol"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = get_gw_logger(self.gateway_instance.id)

    def perform_value_send(self, component, value):
        dev_pk = component.config.get('hello_device')
        device = HelloDevice.objects.filter(pk=dev_pk).first()
        if not device:
            self.logger.warning("no HelloDevice linked; skipping send")
            return
        self.logger.info(f"send to {device} => {value}")
        # Replace with a real device call...
Local authority. Models keep device registries and history on the SIMO.io hub. Admin gives pros the right depth when it matters; the SIMO.io app stays clean for daily use.

Chapter 6 — URLs and views

SIMO.io auto‑discovers simple URL modules in each app. If your app provides an auto_urls.py with a standard urlpatterns list, SIMO.io mounts it automatically at /app_label/.... No project‑level edits required.

Auto‑discovery. Drop auto_urls.py in your app and define urlpatterns. SIMO.io includes it under the app’s label (e.g., /simo_hello/hello-status/).

A tiny hello view

Let’s expose a small status endpoint and a simple HTML page. Place them under standard views.py and wire with auto_urls.py. The resulting URLs will be /simo_hello/hello-status/ and /simo_hello/hello/.

# /etc/SIMO/hub/simo_hello/views.py
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import render


@login_required
def hello_status(request):
    return JsonResponse({"status": "ok"})


@login_required
def hello_page(request):
    return render(request, 'simo_hello/hello_page.html', {"title": "Hello"})
# /etc/SIMO/hub/simo_hello/auto_urls.py
from django.urls import path
from .views import hello_status, hello_page


urlpatterns = [
    path('hello-status/', hello_status, name='hello-status'),
    path('hello/', hello_page, name='hello-page'),
]
# /etc/SIMO/hub/simo_hello/templates/simo_hello/hello_page.html
<div class="container py-4">
  <h2 class="mb-3">Hello from simo_hello</h2>
  <p class="mb-0">This is a tiny, app‑scoped page.</p>
</div>
  • Auth: protect views with @login_required unless you intend them public. Admin helpers often render pages for hub masters.
  • Prefix: SIMO.io mounts your auto_urls under the app label (e.g., simo_hello).
  • Templates: use standard Django paths under templates/app_label/.
  • APIs: for REST endpoints, use api.py with DRF viewsets; SIMO.io auto‑registers them under /api/<instance_slug>/ (see REST API below).
  • Logs: when you open a Gateway (e.g., “Hello Protocol”) in Django Admin you get a live log stream; system service logs live under /var/logs/simo.
  • Static/media: follow Django defaults; SIMO.io serves protected URLs in production.

Chapter 6.1 — REST API (Django REST framework)

For JSON APIs, define a DRF ViewSet in your app’s api.py. SIMO.io auto‑registers any viewset that sets url (path prefix) and basename. No per‑app URL wiring — endpoints appear under /api/<instance_slug>/….

# /etc/SIMO/hub/simo_hello/serializers.py
from rest_framework import serializers
from .models import HelloDevice


class HelloDeviceSerializer(serializers.ModelSerializer):
    class Meta:
        model = HelloDevice
        fields = ("id", "remote_id", "label", "online", "last_seen")
# /etc/SIMO/hub/simo_hello/api.py
from rest_framework import viewsets
from .models import HelloDevice
from .serializers import HelloDeviceSerializer


class HelloDeviceViewSet(viewsets.ReadOnlyModelViewSet):
    url = 'hello/devices'
    basename = 'hello-devices'
    queryset = HelloDevice.objects.all().order_by('label')
    serializer_class = HelloDeviceSerializer
Instance scope. If your data belongs to a specific SIMO.io smart home instance, inherit InstanceMixin from simo.core.api and filter on self.instance in get_queryset(). For hub‑global resources, a plain viewset is fine.
  • URLs: GET /api/<instance_slug>/hello/devices/, GET /api/<instance_slug>/hello/devices/{id}/.
  • Auth: same session/auth as the SIMO.io app; DRF permissions apply.
  • Custom actions: add @action for custom endpoints (e.g., POST operations) when needed.

Chapter 7 — Discovery

Discovery reduces setup to a single, confident gesture. Instead of typing IDs or hunting IP addresses, you put a device into pairing mode and let the gateway find it. It’s faster on a ladder, kinder to non‑developers, and still explicit: you start discovery, you confirm results, you finish. Local, professional, predictable.

When to use discovery Use it when devices can announce themselves (SSDP/mDNS/Z‑Wave/BLE/bus protocols), when you want to pre‑fill config safely, or when a gateway can create several components from a scan (e.g., zones, channels, sensors).

How SIMO.io discovery fits together

  • Discoverable controllers: a controller becomes discoverable by defining _init_discovery(). The SIMO.io app shows a “Discover” flow and the controller’s discovery_msg to guide the user.
  • Start: the controller calls gateway.start_discovery() with initial form data. The gateway begins scanning.
  • Report: as candidates appear, the gateway calls gateway.process_discovery({...}). The controller’s _process_discovery() may create components or return helpful errors.
  • Finish: the user taps Finish; the gateway runs finish_discovery(). If the controller provides _finish_discovery(), it can finalize any remaining pieces.

Make our hello switch discoverable

We’ll add a gentle prompt and two class methods. The controller kicks off discovery; when the gateway finds a device with a remote_id, the controller creates a component from the original form inputs plus the discovered ID.

# /etc/SIMO/hub/simo_hello/controllers.py
from simo.core.controllers import Switch
from simo.core.models import Gateway
from simo.core.utils.serialization import serialize_form_data, deserialize_form_data
from .forms import HelloSwitchConfigForm
from .gateways import HelloGatewayHandler


class StringSwitch(Switch):
    name = "String Switch"
    gateway_class = HelloGatewayHandler
    config_form = HelloSwitchConfigForm
    default_config = {"remote_id": ""}
    discovery_msg = "Put your Hello device into pairing mode."

    @classmethod
    def _init_discovery(self, form_cleaned_data):
        gateway = Gateway.objects.filter(type=self.gateway_class.uid).first()
        gateway.start_discovery(
            self.uid, serialize_form_data(form_cleaned_data), timeout=30
        )

    @classmethod
    def _process_discovery(cls, started_with, data):
        if data.get('discovery-result') == 'fail':
            return { 'error': data.get('error', 'Device not found.') }

        started_with = deserialize_form_data(started_with)
        started_with['remote_id'] = data['result']['config']['remote_id']
        form = HelloSwitchConfigForm(controller_uid=cls.uid, data=started_with)
        if form.is_valid():
            new_component = form.save()
            return [new_component]
        return { 'error': form.errors.as_text() }

Gateway: report discovered devices

Your gateway feeds discoveries back to SIMO.io by calling process_discovery() on its Gateway model instance. In a real driver this would parse SSDP/mDNS packets, poll a bus, or listen for advertisements. Below, we demo reading known HelloDevice rows to simulate discovery.

# /etc/SIMO/hub/simo_hello/gateways.py
from simo.core.gateways import BaseObjectCommandsGatewayHandler
from simo.core.loggers import get_gw_logger
from .models import HelloDevice
from .controllers import StringSwitch


class HelloGatewayHandler(BaseObjectCommandsGatewayHandler):
    name = "Hello Protocol"
    periodic_tasks = (('discover_hello_devices', 5),)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = get_gw_logger(self.gateway_instance.id)
        self._seen = set()

    def discover_hello_devices(self):
        for dev in HelloDevice.objects.filter(online=True):
            if dev.remote_id in self._seen:
                continue
            payload = {
                'type': StringSwitch.uid,
                'discovery-result': 'ok',
                'result': { 'config': { 'remote_id': dev.remote_id } },
            }
            self.gateway_instance.process_discovery(payload)
            self._seen.add(dev.remote_id)
Predictable flow. Start discovery, show progress, confirm results, finish. If nothing shows up, retry or add manually — either way stays local, auditable, and reversible.

Chapter 8 — Dynamic settings

Some knobs belong in the app, not in code. Dynamic settings let you tweak behavior per hub instance without redeploying: poll intervals, extra logging, feature flags. They live under a namespace and flow through the same permissions and audit trail as everything else.

Namespace convention Use <app>__<setting> (double underscore). Example: hello__poll_interval.

Declare settings

# /etc/SIMO/hub/simo_hello/dynamic_settings.py
from dynamic_preferences.preferences import Section
from dynamic_preferences.types import IntegerPreference, BooleanPreference
from dynamic_preferences.registries import global_preferences_registry

hello = Section('hello')


@global_preferences_registry.register
class HelloPollInterval(IntegerPreference):
    section = hello
    name = 'poll_interval'
    default = 5  # seconds
    help_text = 'How often to run hello discovery/polling.'


@global_preferences_registry.register
class HelloDebug(BooleanPreference):
    section = hello
    name = 'debug'
    default = False
    help_text = 'Emit extra gateway debug logs.'

Read settings in code

# /etc/SIMO/hub/simo_hello/gateways.py
import time
from simo.conf import dynamic_settings


class HelloGatewayHandler(BaseObjectCommandsGatewayHandler):
    name = "Hello Protocol"
    periodic_tasks = (('discover_hello_devices', 1),)  # tick fast; we self-throttle

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._last_discovery = 0

    def discover_hello_devices(self):
        # Throttle by dynamic setting
        interval = int(dynamic_settings['hello__poll_interval']) or 5
        if time.time() - self._last_discovery < interval:
            return
        self._last_discovery = time.time()

        debug = bool(dynamic_settings['hello__debug'])
        if debug:
            self.logger.debug("hello discovery tick")
        # ... perform discovery here ...

Chapter 9 — Logging and observability

Logs save time. Every gateway and component has its own rotating log on disk and a handy viewer in Admin. Keep logs tidy: debug for noisy details, info for normal operations, warning/error for trouble.

  • Gateway logs: use get_gw_logger(gateway_id). View live via Admin’s log widget.
  • Component logs: use get_component_logger(component) when a controller needs its own trail.
  • Admin live logs: open a Gateway object in Django Admin (e.g., “Hello Protocol”) to watch a live stream of its logs. This is the primary place to inspect integration behavior.
  • System service logs: hub services write to /var/logs/simo for deeper troubleshooting.
# /etc/SIMO/hub/simo_hello/gateways.py
from simo.core.loggers import get_gw_logger

class HelloGatewayHandler(BaseObjectCommandsGatewayHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = get_gw_logger(self.gateway_instance.id)

    def perform_value_send(self, component, value):
        self.logger.info(f"TX to {component} => {value}")
        try:
            # ... device I/O ...
            pass
        except Exception as e:
            self.logger.error(f"TX error {e}", exc_info=True)

Chapter 10 — Timeouts, retries, and errors

Networks fail. Devices sulk. Build backoffs and clear status into your gateway. Mark components unhealthy when their device stops answering; recover automatically when it wakes up.

# /etc/SIMO/hub/simo_hello/gateways.py
import time


class HelloGatewayHandler(BaseObjectCommandsGatewayHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._retries = {}

    def perform_value_send(self, component, value):
        try:
            # device_call(component, value)
            self._retries.pop(component.id, None)
            component.alive = True
            component.error_msg = None
            component.save(update_fields=['alive', 'error_msg'])
        except Exception as e:
            backoff = self._retries.get(component.id, 0)
            self._retries[component.id] = min(backoff + 1, 5)
            self.logger.warning(f"Retry {self._retries[component.id]} for {component}: {e}")
            component.alive = False
            component.error_msg = str(e)
            component.save(update_fields=['alive', 'error_msg'])
Discipline first. Small retries with jitter, clear errors, and a quick path back to healthy beats complex recovery code.

Chapter 14 — History and events

The hub records value changes, alarms, and actions. This makes troubleshooting and UX features (graphs, timelines) straightforward.

  • Automatic: when a component value or status changes, a history row is added and last_change updates.
  • Visible: History appears on the component page in Admin and via API serializers.
  • Alive + errors: health and error_msg are tracked alongside values.

Chapter 16 — Package and publish

Publish one sharp module and you lift the whole craft. Your code unblocks an installer on a ladder, saves a weekend for a family, and gives another developer a clean starting point. Put your name on it. Keep the README.rst short and kind. Let people find you!

Publish a small, well‑named Python package so others can install it cleanly on their SIMO.io hubs. Use the simo-<integration> naming pattern to make intent obvious (e.g., simo-hello).

README.rst is required. Include a simple README.rst that explains how to install the package on a SIMO.io hub, how to enable it in INSTALLED_APPS, and how to restart services.

Minimal project layout

simo-hello/
├─ pyproject.toml
├─ README.rst
└─ src/
   └─ simo_hello/
      ├─ __init__.py
      ├─ controllers.py
      ├─ gateways.py
      ├─ forms.py
      └─ dynamic_settings.py

pyproject.toml (setuptools)

[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "simo-hello"
version = "0.1.0"
description = "Hello integration for SIMO.io hub"
readme = "README.rst"
requires-python = ">=3.10"
authors = [{ name = "You", email = "you@example.com" }]
license = { text = "MIT" }
keywords = ["simo.io", "smart-home", "integration"]
classifiers = [
  "Programming Language :: Python :: 3",
  "Framework :: Django",
]

[project.urls]
Homepage = "https://github.com/you/simo-hello"

[tool.setuptools]
package-dir = {"" = "src"}

[tool.setuptools.packages.find]
where = ["src"]
include = ["simo_hello*"]

README.rst (example)

SIMO‑Hello
==========

Install
-------

1. SSH to your SIMO.io hub and activate env::

   workon simo-hub

2. Install the package::

   pip install simo-hello

3. Enable the app in ``/etc/SIMO/settings.py``::

   INSTALLED_APPS += ['simo_hello']

4. Restart services::

   supervisorctl restart all

Usage
-----

After you restart services, the "Hello Protocol" gateway is created automatically. Open it in Django Admin to watch logs. Then add a "String Switch" component from the SIMO.io app.

Publish to PyPI

  1. Create a PyPI account and API token.
  2. Build your package: python -m pip install build twine then python -m build.
  3. Upload: python -m twine upload dist/* (use your token when prompted or configure ~/.pypirc).
Simple, public, helpful. A small README.rst and a clean PyPI release make your work easy to discover and install on any SIMO.io hub.

 

Share it on the integrations portal

Publish your integration so the community can discover, try, and improve it.

1) Publish on GitHub

  • Name it clearly: simo-<brand-or-device> (e.g., simo-hello).
  • Include: controllers.py, gateways.py, forms.py, pyproject.toml, requirements.txt, README.rst, and a LICENSE.
  • Set auto_create = True on your gateway.
  • Keep README.rst hub‑focused: install → enable in INSTALLED_APPS → migrate → restart → add components → how to use.
  • Optional: add screenshots/gifs; they help readers understand the value fast.
  • Open Issues/PRs in your repo to manage feedback.
  • .

2) Submit via the portal

  • Go to Integrations on simo.io.
  • If a request for your device exists, open it and click Submit your implementation.
  • If not, click Request integration, then submit your GitHub repo URL on that page.

3) Keep it fresh

  • We review and list your integration; your README is shown on the integration page.
  • Update versions and docs as you improve the code. Clear READMEs get more installs.
  • Use your repo’s Issues for bugs/ideas; use the integration page for general discussion.
Share:

Related pages

Comments

No comments yet.

Sign In to leave a comment

Sign up to our newsletter

Receive latest updates on what's happening on a weekly basis

Top