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.
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 seemanage.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.
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)
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)._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.
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.
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
Componentmodel are stored undercomponent.configas JSON. ConfigFieldsMixin takes care of mapping models to their primary keys. - Default config: set
default_configon the controller to seed sensible defaults when adding new components. - Validation: use
clean()orclean_*to validate and normalize input. Errors surface inline in the app. - Use in runtime: access
self.component.configfrom controllers, orcomponent.configfrom gateways.
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.
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...
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_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_requiredunless you intend them public. Admin helpers often render pages for hub masters. - Prefix: SIMO.io mounts your
auto_urlsunder the app label (e.g.,simo_hello). - Templates: use standard Django paths under
templates/app_label/. - APIs: for REST endpoints, use
api.pywith 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
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
@actionfor 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.
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’sdiscovery_msgto 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)
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.
<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/simofor 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'])
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).
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
- Create a PyPI account and API token.
- Build your package:
python -m pip install build twinethenpython -m build. - Upload:
python -m twine upload dist/*(use your token when prompted or configure~/.pypirc).
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 = Trueon 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.