Python SDK for controlling SIMO.io smart homes over REST+MQTT
Project description
simo-sdk
Quick start
from simo_sdk import SIMOClient
home = SIMOClient(
url="https://hub.example.com",
secret_key="YOUR_SIMO_IO_SECRET_KEY",
instance="my-home", # instance slug OR instance uid
)
light = home.components.filter(name="Main light", zone="Living Room")[0]
light.turn_on()
light.wait_for(fields=["value"], timeout=10)
print(light.value)
print(home.sun.is_night())
print(home.main_state.value)
print(home.weather.value)
Jailed mode (via Unix socket)
This SDK supports running in a networkless environment by talking to a local Unix socket supervisor (a separate hub-side service).
Then use the same SDK from a jailed Python process with no network access:
from simo_sdk import SIMOClient
home = SIMOClient(
socket_path="/run/simo/simo-sdk-supervisor.sock",
token="SCRIPT_RUN_TOKEN",
instance="my-home",
)
If the hub starts the jailed process with env vars, you can keep scripts extra clean:
SIMO_SDK_SOCKET_PATHSIMO_SDK_TOKENSIMO_SDK_INSTANCE
Then automations can just do:
from simo_sdk import SIMOClient
home = SIMOClient()
Install
pip install -e packages/simo-sdk
Tests
Run unit tests (no hub required):
python -m unittest simo_sdk.tests.test_env_defaults
python -m unittest simo_sdk.tests.test_on_change_since
SIMOClient
Type: simo_sdk.client.SIMOClient
home = SIMOClient(url="https://hub.example.com", secret_key="...", instance="my-home")
Parameters:
url: str– URL of your SIMO.io hubsecret_key: str– your SIMO.io user secret keyinstance: str– instance slug or instance uidverify_ssl: bool | str | None– optional (LAN hubs with self-signed certs are supported automatically)
If MQTT is not reachable, the client keeps retrying in the background.
Attributes:
home.zones: simo_sdk.collections.Zoneshome.categories: simo_sdk.collections.Categorieshome.components: simo_sdk.collections.Componentshome.users: simo_sdk.collections.Usershome.sun: simo_sdk.sun.LocalSunhome.main_state: simo_sdk.models.Component | Nonehome.weather: simo_sdk.models.Component | None
home.sun
Type: simo_sdk.sun.LocalSun
Use this for time-of-day logic (sunrise/sunset/night checks) in automations.
Methods:
now() -> datetime- Current time in the instance timezone (if available).
is_night(localdatetime=None) -> boolTruewhen current time is outside sunrise–sunset.
get_sunrise_time(localdatetime=None) -> datetimeget_sunset_time(localdatetime=None) -> datetimeseconds_to_sunrise(localdatetime=None) -> floatseconds_to_sunset(localdatetime=None) -> float
Examples:
if home.sun.is_night():
print("It's dark")
print("sunrise:", home.sun.get_sunrise_time(home.sun.now()))
print("sunset:", home.sun.get_sunset_time(home.sun.now()))
home.main_state
Type: simo_sdk.models.Component | None
This is the SIMO.io "Main State" component (default name: "Main State").
It represents a single global state for the whole smart home and is meant to be used by automations as a shared context.
Common states include (by default):
morningdayeveningnightsleepawayvacation
Usage:
state = home.main_state.value if home.main_state else None
if state in ("away", "vacation"):
print("Skip some automations")
# change state (use configured state slugs)
if home.main_state:
home.main_state.send("sleep")
home.weather
Type: simo_sdk.models.Component | None
This is the SIMO.io "Weather" component.
It provides current outdoor weather information for your instance location. In automations it is typically used to check:
- temperature / feels_like
- wind
- rain/snow indicators
- general condition
Usage:
if home.weather:
w = home.weather.value or {}
temp = (w.get("main") or {}).get("temp")
feels = (w.get("main") or {}).get("feels_like")
condition = ((w.get("weather") or [{}])[0] or {}).get("main")
print(temp, feels, condition)
Typical home.weather.value shape (example):
{
"dt": 1766408550,
"id": 597881,
"cod": 200,
"sys": {
"id": 1880,
"type": 1,
"sunset": 1766411955,
"country": "LT",
"sunrise": 1766386119
},
"base": "stations",
"main": {
"temp": 4.33,
"humidity": 88,
"pressure": 1023,
"temp_max": 4.33,
"temp_min": 4.33,
"sea_level": 1023,
"feels_like": 2.45,
"grnd_level": 1014
},
"name": "Kulautuva",
"wind": {
"deg": 304,
"gust": 3.26,
"speed": 2.13
},
"coord": {
"lat": 54.9383,
"lon": 23.6476
},
"clouds": {
"all": 36
},
"weather": [
{
"id": 802,
"icon": "03d",
"main": "Clouds",
"description": "scattered clouds"
}
],
"timezone": 7200,
"visibility": 10000
}
Zones
Zone type: simo_sdk.models.Zone
id: intname: str
Lookup:
zone_by_id = home.zones[12]
zone_by_name = home.zones["Kitchen"]
Categories
Category type: simo_sdk.models.Category
id: intname: str
Lookup:
cat_by_id = home.categories[5]
cat_by_name = home.categories["Lights"]
Components
Get by id
lamp = home.components[123]
Filter
Type returned by filter(...): simo_sdk.collections.ComponentQuery (iterable)
# by name (substring match, case-insensitive)
items = home.components.filter(name="light")
# by base_type
items = home.components.filter(base_type="switch")
# by zone/category using name
items = home.components.filter(zone="Kitchen", category="Lights")
# by zone/category using objects
z = home.zones["Kitchen"]
c = home.categories["Lights"]
items = home.components.filter(zone=z, category=c)
Filter parameters:
name: str | Nonebase_type: str | Nonezone: int | str | simo_sdk.models.Zone | Nonecategory: int | str | simo_sdk.models.Category | None
Order
items = home.components.filter(zone="Kitchen")
# default ordering is by id
items = items.order_by() # same as order_by("id")
# order by any field
items = items.order_by("name")
items = items.order_by("-last_change")
# multi-field ordering
items = items.order_by("zone_id", "name")
Component
Component type: simo_sdk.models.Component
Actions
lamp.turn_on()
lamp.turn_off()
lamp.toggle()
blinds = home.components.filter(base_type="blinds")[0]
blinds.open()
blinds.close()
lamp.send(True)
lamp.call("set_volume", 30)
If the method exists on the component, you can call it directly:
lamp.set_volume(30)
Slave components
If your component has slave components (for example a multi-switch), you can control a slave like this:
multi = home.components.filter(name="Kitchen switch")[0]
slave = multi.slave(multi.slaves[0])
slave.turn_on()
Waiting for a state update
lamp.turn_on()
lamp.wait_for(fields=["value"], timeout=10)
print(lamp.value)
Reacting to changes
def changed(c):
print(c.name, c.value)
lamp.on_change(changed, fields=["value"])
You can also receive the actor:
def changed(c, actor):
if actor and actor.type == "user" and actor.user:
print("changed by:", actor.user.name)
else:
print("changed by:", actor.type)
lamp.on_change(changed, fields=["value"])
Refresh
lamp.refresh()
print(lamp.value)
Fields
These are exposed as attributes:
id: intname: stricon: str | Nonebase_type: str | Nonezone_id: int | Nonecategory_id: int | Nonegateway_id: int | Noneshow_in_app: bool | Nonecontroller_uid: str | Nonelast_change: float | Nonelast_modified: float | Noneread_only: bool | Noneslaves: list[int]info: Anyvalue: Anyvalue_units: str | Nonemeta: dictconfig: dictalive: bool | Noneerror_msg: str | Nonealarm_category: str | Nonearm_status: str | Nonebattery_level: int | Nonecontroller_methods: list[str]
Additional fields (including future SIMO.io fields) are always available here:
data: dict
Users
User type: simo_sdk.models.User (this represents an InstanceUser in SIMO.io)
Current user
me = home.users.me
print(me.name, me.at_home)
Filter
home_users = home.users.filter(is_active=True)
people_at_home = home.users.filter(at_home=True)
Notify a user
u = home.users.filter(name="Simon")[0]
u.notify(severity="warning", title="Hello", body="Test")
Reacting to user changes
def presence_changed(u):
print(u.name, "at_home:", u.at_home)
home.users.me.on_change(presence_changed, fields=["at_home"])
User fields
id: int(InstanceUser id)user_id: int | None(global User id)email: str | Nonename: str | Nonerole_id: int | Nonerole_name: str | Nonerole_is_owner: bool | Nonerole_is_superuser: bool | Nonerole_can_manage_users: bool | Nonerole_is_person: bool | Noneis_active: bool | Noneat_home: bool | Nonelast_seen: float | None(timestamp)last_seen_location: str | None("lat,lon")last_seen_speed_kmh: float | Nonephone_on_charge: bool | None
Actor
Actor type: simo_sdk.models.Actor
type: str– one of:user,device,system,aiuser: simo_sdk.models.User | None
Note: actor.user is only set when actor.type == "user".
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file simo_sdk-1.1.2.tar.gz.
File metadata
- Download URL: simo_sdk-1.1.2.tar.gz
- Upload date:
- Size: 23.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
264aad4dc2c2824388c239c4812bb4b16cbc40c366a9b4fc3ed9050a3549e6ac
|
|
| MD5 |
85cb2257156daa43734469289e521484
|
|
| BLAKE2b-256 |
09043a0b99fede74125e7f40a1defdbbd10e96f5560649fc378e0278ea23cceb
|
File details
Details for the file simo_sdk-1.1.2-py3-none-any.whl.
File metadata
- Download URL: simo_sdk-1.1.2-py3-none-any.whl
- Upload date:
- Size: 24.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1124530eb249d59fb7d2de88b88771ef36c31936671e61c1548e6bbebaf1b240
|
|
| MD5 |
8adfa49c3341f0057fad0df9c99ff99b
|
|
| BLAKE2b-256 |
f9a5d0d93155db8fefcdcdae70cec3e63f1afc9cdd871dcba966b2b7c53561e0
|