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".
Metadata
Release files for simo-sdk 1.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| simo_sdk-1.1.2.tar.gz | 23.8 kB | Without provenance | Details for simo_sdk-1.1.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| simo_sdk-1.1.2-py3-none-any.whl | Python 3 | none | any | Details for simo_sdk-1.1.2-py3-none-any.whl |
Total release size: 47.8 kB
Release files / simo_sdk-1.1.2.tar.gz
| Download URL | simo_sdk-1.1.2.tar.gz |
|---|---|
| Size | 23.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
264aad4dc2c2824388c239c4812bb4b16cbc40c366a9b4fc3ed9050a3549e6ac
|
|
BLAKE2b-256 checksum How to use checksums |
09043a0b99fede74125e7f40a1defdbbd10e96f5560649fc378e0278ea23cceb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.0.1 CPython/3.12.3
|
Release files / simo_sdk-1.1.2-py3-none-any.whl
| Download URL | simo_sdk-1.1.2-py3-none-any.whl |
|---|---|
| Size | 24.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1124530eb249d59fb7d2de88b88771ef36c31936671e61c1548e6bbebaf1b240
|
|
BLAKE2b-256 checksum How to use checksums |
f9a5d0d93155db8fefcdcdae70cec3e63f1afc9cdd871dcba966b2b7c53561e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.0.1 CPython/3.12.3
|
Security for simo-sdk 1.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.