[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build an IoT Device Dashboard

Build an IoT Device Dashboard

Build an IoT device management dashboard with real-time telemetry, device provisioning, OTA firmware updates, alerting, and fleet management for connected device deployments.

Development#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 4 skills

Avg quality 97/100·All SAFE
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Olga leads engineering at a 20-person IoT company with 10,000 deployed sensors (temperature, humidity, motion) in 200 buildings. Devices report data but there's no centralized dashboard — checking a device requires SSH. 15% of devices are offline but nobody knows until a customer complains. Firmware updates require physical access to each device. There's no alerting — a freezer temperature sensor reading 25°C (should be -18°C) goes unnoticed. They need a dashboard: real-time telemetry, device health monitoring, OTA updates, configurable alerts, and fleet-wide analytics.

Step 1: Build the Device Dashboard

typescript
import { pool } from "../db";
import { Redis } from "ioredis";
import { randomBytes } from "node:crypto";
const redis = new Redis(process.env.REDIS_URL!);

interface Device {
  id: string;
  name: string;
  type: "temperature" | "humidity" | "motion" | "energy" | "air_quality";
  location: { building: string; floor: string; room: string; lat?: number; lng?: number };
  firmware: string;
  status: "online" | "offline" | "error" | "updating";
  lastSeenAt: string;
  config: Record<string, any>;
  tags: string[];
}

interface TelemetryPoint { deviceId: string; metric: string; value: number; unit: string; timestamp: number; }
interface Alert { id: string; deviceId: string; metric: string; value: number; threshold: number; condition: "above" | "below"; severity: "critical" | "warning"; message: string; acknowledgedAt: string | null; createdAt: string; }
interface AlertRule { id: string; deviceType: string; metric: string; condition: "above" | "below"; threshold: number; severity: "critical" | "warning"; message: string; }

const ALERT_RULES: AlertRule[] = [
  { id: "temp-high", deviceType: "temperature", metric: "temperature", condition: "above", threshold: 30, severity: "warning", message: "Temperature above 30°C" },
  { id: "temp-critical", deviceType: "temperature", metric: "temperature", condition: "above", threshold: 40, severity: "critical", message: "Temperature critical: above 40°C" },
  { id: "temp-freezer", deviceType: "temperature", metric: "temperature", condition: "above", threshold: -10, severity: "critical", message: "Freezer temperature above -10°C" },
  { id: "humidity-high", deviceType: "humidity", metric: "humidity", condition: "above", threshold: 80, severity: "warning", message: "Humidity above 80%" },
  { id: "motion-offline", deviceType: "motion", metric: "battery", condition: "below", threshold: 10, severity: "warning", message: "Battery below 10%" },
];

// Ingest telemetry from device
export async function ingestTelemetry(points: TelemetryPoint[]): Promise<void> {
  const pipeline = redis.pipeline();
  for (const point of points) {
    // Latest value
    pipeline.hset(`device:${point.deviceId}:latest`, point.metric, JSON.stringify({ value: point.value, unit: point.unit, timestamp: point.timestamp }));
    // Time series (keep 24h at 1min resolution)
    const minute = Math.floor(point.timestamp / 60000);
    pipeline.hset(`telemetry:${point.deviceId}:${minute}`, point.metric, point.value);
    pipeline.expire(`telemetry:${point.deviceId}:${minute}`, 86400);
    // Mark device as seen
    pipeline.set(`device:lastseen:${point.deviceId}`, point.timestamp);
    pipeline.expire(`device:lastseen:${point.deviceId}`, 600); // 10 min TTL
  }
  await pipeline.exec();

  // Check alert rules
  for (const point of points) {
    await checkAlerts(point);
  }
}

async function checkAlerts(point: TelemetryPoint): Promise<void> {
  const { rows: [device] } = await pool.query("SELECT type, name, location FROM devices WHERE id = $1", [point.deviceId]);
  if (!device) return;

  for (const rule of ALERT_RULES) {
    if (rule.deviceType !== device.type || rule.metric !== point.metric) continue;
    const triggered = rule.condition === "above" ? point.value > rule.threshold : point.value < rule.threshold;

    if (triggered) {
      const dedupeKey = `alert:dedup:${point.deviceId}:${rule.id}`;
      if (await redis.exists(dedupeKey)) continue; // already alerted recently

      await redis.setex(dedupeKey, 3600, "1"); // dedup for 1 hour
      const alertId = `alert-${randomBytes(6).toString("hex")}`;
      const location = JSON.parse(device.location);

      await pool.query(
        `INSERT INTO device_alerts (id, device_id, device_name, metric, value, threshold, condition, severity, message, location, created_at)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())`,
        [alertId, point.deviceId, device.name, point.metric, point.value, rule.threshold, rule.condition, rule.severity, `${rule.message} — ${device.name} at ${location.building}/${location.room}`, JSON.stringify(location)]
      );

      await redis.rpush("notification:queue", JSON.stringify({ type: "iot_alert", alertId, severity: rule.severity, device: device.name, metric: point.metric, value: point.value, threshold: rule.threshold }));
    }
  }
}

// Get fleet overview
export async function getFleetOverview(): Promise<{ total: number; online: number; offline: number; alerting: number; byType: Record<string, number>; byBuilding: Record<string, number> }> {
  const { rows: devices } = await pool.query("SELECT id, type, location FROM devices");
  let online = 0, offline = 0, alerting = 0;
  const byType: Record<string, number> = {};
  const byBuilding: Record<string, number> = {};

  for (const device of devices) {
    const lastSeen = await redis.get(`device:lastseen:${device.id}`);
    if (lastSeen) online++; else offline++;
    byType[device.type] = (byType[device.type] || 0) + 1;
    const loc = JSON.parse(device.location);
    byBuilding[loc.building] = (byBuilding[loc.building] || 0) + 1;
  }

  const { rows: [{ count: alertCount }] } = await pool.query("SELECT COUNT(*) as count FROM device_alerts WHERE acknowledged_at IS NULL AND created_at > NOW() - INTERVAL '24 hours'");
  alerting = parseInt(alertCount);

  return { total: devices.length, online, offline, alerting, byType, byBuilding };
}

// Get device telemetry history
export async function getDeviceTelemetry(deviceId: string, hours: number = 24): Promise<Array<{ timestamp: number; metrics: Record<string, number> }>> {
  const now = Math.floor(Date.now() / 60000);
  const data: any[] = [];
  for (let m = now - hours * 60; m <= now; m += 5) { // 5 min resolution
    const metrics = await redis.hgetall(`telemetry:${deviceId}:${m}`);
    if (Object.keys(metrics).length > 0) {
      data.push({ timestamp: m * 60000, metrics: Object.fromEntries(Object.entries(metrics).map(([k, v]) => [k, parseFloat(v)])) });
    }
  }
  return data;
}

// Schedule OTA firmware update
export async function scheduleOTAUpdate(deviceIds: string[], firmwareVersion: string, firmwareUrl: string): Promise<{ scheduled: number }> {
  for (const deviceId of deviceIds) {
    await pool.query(
      "INSERT INTO ota_updates (device_id, firmware_version, firmware_url, status, scheduled_at) VALUES ($1, $2, $3, 'pending', NOW())",
      [deviceId, firmwareVersion, firmwareUrl]
    );
    await redis.rpush(`device:commands:${deviceId}`, JSON.stringify({ command: "ota_update", firmwareUrl, version: firmwareVersion }));
  }
  return { scheduled: deviceIds.length };
}

// Get active alerts
export async function getActiveAlerts(): Promise<Alert[]> {
  const { rows } = await pool.query("SELECT * FROM device_alerts WHERE acknowledged_at IS NULL ORDER BY severity, created_at DESC LIMIT 100");
  return rows;
}

// Acknowledge alert
export async function acknowledgeAlert(alertId: string, userId: string): Promise<void> {
  await pool.query("UPDATE device_alerts SET acknowledged_at = NOW(), acknowledged_by = $2 WHERE id = $1", [alertId, userId]);
}

Results

  • 15% offline devices found — fleet overview shows 1,500 offline out of 10,000; ops team investigates; network issue in Building 7 fixed; uptime: 85% → 98%
  • Freezer alert in 30 seconds — temperature sensor reads 25°C → critical alert fires → facility manager notified → compressor restarted; $50K of inventory saved
  • OTA firmware updates — schedule update for 10,000 devices from dashboard; devices pull update on next check-in; no physical access; rollout in hours not weeks
  • Real-time telemetry — click any device → see temperature, humidity, battery in real-time; 24-hour history chart; anomalies visible at a glance
  • Fleet analytics — 200 buildings, 10K devices, grouped by type and location; identify buildings with most offline devices; prioritize maintenance

Related use cases