How do I make two devices talk to each other?

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 your devicesdk.ts devices block.
  • The RPC types come from devicesdk-env.d.ts, regenerated by devicesdk 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 the env/ctx properties 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 emitState and updates an OLED.
  • Persist the last-seen temperature on the controller so it can recover after a reboot.