Core Concepts
Six concepts cover almost everything you will write. Each is small.
Device
A physical thing on a network. A Modbus PLC, a BLE sensor, a GPIO contact. You declare its connection and the registers you want to read.
class TempSensor(Device):
id = "temp-1"
connection = modbus_tcp(host="192.168.1.50", port=502, slave=1)
poll = every(5, SECONDS)
registers = [
Register(40001, "temperature", unit="C", scale=0.1),
]id is unique within your project. The runtime uses it as the topic prefix and the directory name on the gateway.
Register
One value the device exposes. Address, name, optional unit and scale. Registers can be read-only or writable depending on the address range.
Register(40001, "temperature", unit="C", scale=0.1)
Register(40003, "flow", dtype="uint32", endianness="little")
Register(40005, "setpoint", writable=True)The four parameters that matter for Modbus are dtype, endianness, on_error, and scale. See Modbus for the full breakdown.
Controller
Business logic. A class with methods that fire on events. Reads from devices, publishes telemetry, sends alerts, writes to actuators.
class SafetyMonitor(Controller):
@on.interval(5, SECONDS)
def check(self):
if TempSensor.temperature > 95:
self.alert("critical", f"Overheating: {TempSensor.temperature}C")The decorator picks when the method runs. The body is plain Python.
Triggers
The @on.* family. Pick the one that matches your need.
@on.interval(5, SECONDS) # periodic
@on.threshold(TempSensor.temperature, above=80) # value crossing
@on.change(Door.state) # any change
@on.message("commands/set-target") # inbound MQTT command
@on.startup # gateway boot
@on.shutdown # gateway stopAll controllers are event-driven. There is no main loop you write yourself.
Topics
Project-level string constants for publish topics and message subscriptions. Eliminates string-typo bugs that the compiler cannot catch otherwise.
class Topics(Topics):
SENSOR_DATA = "sensor-data"
SET_TARGET = "set-target"
# anywhere in your controllers
self.publish(Topics.SENSOR_DATA, {...})
@on.message(Topics.SET_TARGET)
def update(self, message): ...If you reference a Topics.X that does not exist, the compiler tells you at build time, not at first MQTT message.
Targets
Where your project will run. Three targets exist today:
- linux is production. Runs on Raspberry Pi, industrial Linux boxes, anything with systemd.
- esp32 is preview. DSL accepted, runtime in progress.
- rtos is preview. Same status as esp32.
Each target supports a different set of protocols and data types. The compiler enforces these limits at validate time, so you cannot accidentally write code that fits Linux but breaks ESP.
scadable verify --target esp32 # confirm fit before runtime ships
scadable compile --target linux # actually build for LinuxHow they fit together
You declare devices in devices/. You declare controllers in controllers/ that read from those devices and publish via topics. You compile for a target. The gateway runtime takes the resulting bundle and runs it.
That is the whole authoring model. The next section covers the CLI commands that move you through it.
Next steps
- scadable init: create a fresh project
- scadable add device: scaffold a device
- Modbus: the production protocol surface in v0.2.0
Updated 4 days ago
