How do I make two devices talk to each other?
Use this.env.DEVICES["other"].method() — typed RPC mediated by the runtime
A sensor in one room reads the temperature; a controller in another room turns a fan on or off. Both are DeviceSDK devices in the same project. They talk to each other via the runtime — never directly over the network.
devicesdk.ts
import { defineConfig } from "@devicesdk/cli";
export default defineConfig({
projectId: "two-room-climate",
devices: {
sensor: {
className: "Sensor",
main: "./src/devices/sensor.ts",
deviceType: "pico-w",
wifi: { ssid: "YOUR_WIFI_SSID", password: "YOUR_WIFI_PASSWORD" },
},
controller: {
className: "Controller",
main: "./src/devices/controller.ts",
deviceType: "pico-w",
wifi: { ssid: "YOUR_WIFI_SSID", password: "YOUR_WIFI_PASSWORD" },
},
},
});
After editing the devices block, run devicesdk build so devicesdk-env.d.ts regenerates — that’s where the inter-device RPC types come from.
src/devices/sensor.ts
import { DeviceEntrypoint, type DeviceResponse } from "@devicesdk/core";
export class Sensor extends DeviceEntrypoint {
crons = { sample: "*/1 * * * *" };
async onCron() {
await this.env.DEVICE.getTemperature();
}
async onMessage(message: DeviceResponse) {
if (message.type !== "temperature_result") return;
// RPC call — typed against the controller's public methods.
await this.env.DEVICES["controller"].handleTemperature(message.payload.celsius);
}
}
src/devices/controller.ts
import { DeviceEntrypoint, OnboardLED, type DeviceResponse } from "@devicesdk/core";
export class Controller extends DeviceEntrypoint {
/**
* Public method — callable from other devices via
* await this.env.DEVICES["controller"].handleTemperature(c);
* Lifecycle hooks (onDeviceConnect, etc.) and `env`/`ctx` are *not* exposed
* on the RPC surface; only your own public methods are.
*/
async handleTemperature(celsius: number) {
if (celsius > 25) {
// Turn the fan on (here represented by the onboard LED).
await this.env.DEVICE.setGpioState(OnboardLED, "high");
console.log(`fan on @ ${celsius}°C`);
} else if (celsius < 23) {
await this.env.DEVICE.setGpioState(OnboardLED, "low");
console.log(`fan off @ ${celsius}°C`);
}
}
async onMessage(_message: DeviceResponse) {
// The controller's own hardware events would be handled here.
}
}
What this demonstrates
this.env.DEVICES["other-slug"].method(arg)is the canonical inter-device RPC.- The slug (
"controller") matches the key in yourdevicesdk.tsdevicesblock. - The RPC types come from
devicesdk-env.d.ts, regenerated bydevicesdk build. - The RPC is runtime-mediated — the sensor never opens a direct network connection to the controller. This means it works whether the two devices are on the same WiFi or on opposite sides of the planet.
- Lifecycle hooks (
onDeviceConnect,onMessage, …) and theenv/ctxproperties are deliberately stripped from the RPC surface. Only the methods you define on the class are callable.
What to do when the controller is offline
this.env.DEVICES["controller"].handleTemperature(...) returns a Promise that rejects if the controller is offline. Handle it explicitly — otherwise the rejection is logged but the sensor keeps trying every minute regardless.
try {
await this.env.DEVICES["controller"].handleTemperature(c);
} catch (err) {
console.warn(`controller unreachable, skipping this tick: ${err instanceof Error ? err.message : err}`);
}
Going further
- Add a third device — a display in a third room — that listens via
emitStateand updates an OLED. - Persist the last-seen temperature on the controller so it can recover after a reboot.