Skip to main content

otensor

PyPI Python License

Python library for writing IoT automations as code — react to device events, run schedules, and trigger actions or notifications on the Otensor platform. Runs anywhere (laptop, server), not on the device itself.

PT-BR — Biblioteca Python para escrever automações IoT em código: reagir a eventos de dispositivos, rodar agendamentos e disparar ações ou notificações na plataforma Otensor. Roda em qualquer lugar (laptop, servidor), não no dispositivo. A documentação abaixo está em inglês.

from otensor import connect

bot = connect()  # reads OTENSOR_API_KEY / OTENSOR_API_BASE_URL

bot.when("living-room-sensor", "temperature").above(30).do("fan", "turn_on")
bot.every_day("18:00").do("porch-light", "turn_on")

bot.run()

Which package do I need?

Otensor ships two Python packages with different jobs:

otensor (this one) otensor-sdk
Runs Anywhere (laptop, server) On the hardware (Raspberry Pi, Linux board)
Job React to events, write automations Publish telemetry, execute commands
Talks to REST API MQTT broker + REST API

Use both if you want a device that reports data and logic that reacts to it. They are independent — neither requires the other.

Requirements

  • Python 3.11+
  • An Otensor instance (self-hosted or managed) and its base_url
  • An API key (sk-…) generated in the dashboard

Install

pip install otensor

Optional extras: otensor[email] (SMTP alerts), otensor[industrial] (Modbus/OPC-UA/InfluxDB), otensor[integrations] (Google Calendar, Slack), or otensor[all] for everything.

Configuration

connect()/Client() read their config from environment variables (loaded from a .env file automatically, or from whatever's already exported) — no need to pass them as arguments every time:

OTENSOR_API_KEY=sk-...
OTENSOR_API_BASE_URL=http://localhost:8000   # or https://api.your-otensor.example

Pass api_key/base_url explicitly to override the corresponding env var — useful for multi-instance scripts or tests. Neither has a hardcoded literal default: a value baked into the library would silently point at the wrong instance when you switch environments. If a value is missing from both the argument and the environment, Client raises ValueError right away.

Two modes

Simple mode — fluent chaining

No classes, no decorators. Best for straightforward rules.

from otensor import connect

bot = connect()

bot.when("living-room-sensor", "temperature").above(30).do("fan-relay", "turn_on")
bot.when("living-room-sensor", "temperature").below(25).do("fan-relay", "turn_off")

bot.when("garage-door", "status").equals("open") \
   .notify(email="owner@home.com", message="Garage door was opened!")

bot.weekdays("08:00").do("office-gate", "open")
bot.every_day("06:00").do("garden-valve", "open")

bot.run()  # blocks until Ctrl-C

Advanced mode — decorators + AutomationContext

Full control: multiple conditions, custom logic, direct access to the event context.

from otensor import Client, on, schedule, when

client = Client(api_key="sk-...", base_url="http://localhost:8000")


@on(client.device("living-room-sensor").property("temperature").above(30))
def turn_on_fan(ctx):
    ctx.command("fan-relay", action="turn_on")
    ctx.log(f"Fan turned on — temperature: {ctx.value}°C")


@on(client.device("living-room-sensor").property("temperature").below(25))
@when(lambda ctx: ctx.value is not None)
def turn_off_fan(ctx):
    ctx.command("fan-relay", action="turn_off")


@schedule("0 8 * * 1-5", client=client)
def office_opening(ctx):
    ctx.command("office-gate", action="open")
    ctx.notify(channel="whatsapp", message="Office is open.")


client.run()

test_mode() and simulate() let you exercise handlers without a live MQTT connection — useful in unit tests:

bot = connect(api_key="sk-test", base_url="http://localhost:8000").test_mode()
bot.when("sensor-01", "temperature").above(30).do("fan-relay", "turn_on")
bot.simulate("sensor-01", "temperature", 35)

API reference

Simple mode (SimpleBot)

Symbol Purpose
connect(api_key=None, *, base_url=None, tenant_id=None) -> SimpleBot Entry point; api_key/base_url fall back to OTENSOR_API_KEY/OTENSOR_API_BASE_URL
.when(device_id, property_name) Start a condition — chain .above(v) / .below(v) / .equals(v) / .changes()
...trigger.do(target_device, action) Run an action when the condition matches
...trigger.notify(email=None, *, message="", channel=None) Send an email and/or a channel notification
.every_day(time) / .weekdays(time) / .weekends(time) Start a schedule (time is "HH:MM") — chain .do(device_id, action)
.run() Connect to MQTT and block until Ctrl-C
.test_mode() / .simulate(device_id, property_name, value) Exercise handlers without a live connection

Advanced mode (Client, decorators, AutomationContext)

Symbol Purpose
Client(api_key=None, *, base_url=None, tenant_id=None) Entry point; api_key/base_url fall back to OTENSOR_API_KEY/OTENSOR_API_BASE_URL
client.device(device_id).property(name) Build a condition — same .above/.below/.equals/.changes() chain
@on(condition) Register a handler for a device event
@schedule(cron, *, client=None) Register a handler on a cron schedule
@when(predicate) Extra filter stacked on top of @on
client.run() Connect to MQTT, start the scheduler, block until Ctrl-C
ctx.value / .device_id / .property / .unit / .ts Event data injected into every handler
ctx.command(device_id, *, action, payload=None, slot=None) Send a tracked command (ack/history)
ctx.send_email(to, *, subject="", body="") Send an email via the platform
ctx.notify(*, channel="email", message="") Send a notification via the platform
ctx.log(message, *, level="info") Structured log
ctx.history(n=10) Last n readings for the current device/property
ctx.audit(*, action, details=None) Write an audit entry

Integrations

Symbol Purpose
Email.configure(*, smtp_host, smtp_port=587, username, password, from_addr) One-time SMTP setup (otensor[email])
Email.send(to, *, subject="", body="") Send an email standalone (outside a handler)
Webhook.post(url, *, payload=None, headers=None, timeout=10.0) POST JSON to any URL, raises on non-2xx

Roadmap

Not in 0.1 — dropped when the public API was cut down to what actually works, tracked for a later release:

  • Duration filters (for_duration(minutes=...)) — trigger only after a condition holds for a period, not on the first match.
  • Pipeline — chained source → publish → condition → action, for direct hardware reads (e.g. Modbus) without going through a device already published to the platform.
  • @debounce — suppress repeated firings within a time window.
  • @learn — record handler behavior for the AI Engine to learn from.
  • @predictive — trigger on ML-detected anomalies rather than fixed thresholds.
  • ctx.ask_user() — pause a handler and wait for human input via notification.

Versioning

Semantic versioning. While on 0.x, a minor bump may carry breaking changes — pin the minor version in production:

otensor~=0.1.0

License

MIT

Release files for otensor 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for otensor 0.2.0
File Size Uploaded
otensor-0.2.0.tar.gz 98.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for otensor 0.2.0
File Interpreter ABI Platform
otensor-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 118.5 kB

Release files / otensor-0.2.0.tar.gz

Download URL otensor-0.2.0.tar.gz
Size 98.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1b2bc2d637fd2a74f37278678863722495550bc91a8adda7f33828585d6e979f
BLAKE2b-256 checksum
How to use checksums
e3bc7c9e67bd3e14543cae24b2da5f275af4b791d9265facca840de9506aa0f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release files / otensor-0.2.0-py3-none-any.whl

Download URL otensor-0.2.0-py3-none-any.whl
Size 19.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ba7947db1dac3e4bb98b00f24a0597f9491ce4194e530032940cd07dd8a6b23f
BLAKE2b-256 checksum
How to use checksums
8475ff811d325305347e994eec3890c32c1858122e8fedc71b114ee64b4cc5d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page