How do I send a daily summary on a cron schedule?

How do I send a daily summary on a cron schedule?

Declare a UTC cron, accumulate values in KV, post a summary once per day

A common pattern for environmental devices: sample frequently, aggregate, send once per day.

devicesdk.ts

import { defineConfig } from "@devicesdk/cli";

export default defineConfig({
  projectId: "daily-summary",
  devices: {
    main: {
      className: "DailySummary",
      main: "./src/devices/main.ts",
      deviceType: "pico-w",
      wifi: { ssid: "YOUR_WIFI_SSID", password: "YOUR_WIFI_PASSWORD" },
    },
  },
});

Then set the webhook URL once:

devicesdk env set SUMMARY_WEBHOOK_URL=https://discord.com/api/webhooks/...

src/devices/main.ts

import { DeviceEntrypoint, type DeviceResponse } from "@devicesdk/core";

interface Window {
  startedAt: number;
  count: number;
  sumC: number;
  minC: number;
  maxC: number;
}

const FRESH: Window = {
  startedAt: 0,
  count: 0,
  sumC: 0,
  minC: Number.POSITIVE_INFINITY,
  maxC: Number.NEGATIVE_INFINITY,
};

export class DailySummary extends DeviceEntrypoint {
  crons = {
    sample: "*/15 * * * *", // every 15 min UTC — collect a reading
    summary: "0 8 * * *",    // every day at 08:00 UTC — post the summary
  };

  async onCron(name: string) {
    if (name === "sample") {
      await this.env.DEVICE.getTemperature();
    } else if (name === "summary") {
      await this.postSummary();
    }
  }

  async onMessage(message: DeviceResponse) {
    if (message.type !== "temperature_result") return;
    const w = (await this.env.DEVICE.kv.get<Window>("window")) ?? {
      ...FRESH,
      startedAt: Date.now(),
    };
    const c = message.payload.celsius;
    const next: Window = {
      startedAt: w.startedAt || Date.now(),
      count: w.count + 1,
      sumC: w.sumC + c,
      minC: Math.min(w.minC, c),
      maxC: Math.max(w.maxC, c),
    };
    await this.env.DEVICE.kv.put("window", next);
  }

  private async postSummary() {
    const w = await this.env.DEVICE.kv.get<Window>("window");
    if (!w || w.count === 0) {
      console.log("No samples in window — skipping summary.");
      return;
    }
    const url = await this.env.VARS.get("SUMMARY_WEBHOOK_URL");
    if (!url) {
      console.error("SUMMARY_WEBHOOK_URL not set — run `devicesdk env set`.");
      return;
    }

    const avg = (w.sumC / w.count).toFixed(1);
    const body = JSON.stringify({
      content: `📊 Daily summary: ${w.count} samples, avg ${avg}°C (min ${w.minC.toFixed(1)}, max ${w.maxC.toFixed(1)})`,
    });

    const res = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body,
    });
    if (!res.ok) {
      console.error(`Webhook POST failed: ${res.status} ${res.statusText}`);
      return;
    }

    // Reset the window for the next day.
    await this.env.DEVICE.kv.put("window", { ...FRESH, startedAt: Date.now() });
  }
}

What this demonstrates

  • Multiple named crons in one device.
  • Accumulating state in KV across many invocations (the runtime is stateless — your class instance does not persist between events).
  • Posting to an external webhook with a properly-handled non-2xx response.
  • Resetting the aggregation window after a successful post.

Notes

  • Crons fire in UTC. Pick a time that lines up with your timezone — 0 8 * * * is 08:00 UTC, which is 09:00 BST or 03:00 CDT.
  • If the webhook is down at 08:00, the script logs the failure and doesn’t reset the window. The next day’s run will include yesterday’s samples too — consider whether that matches your intent.
  • For multiple devices contributing to one summary, see the two-device RPC recipe.