[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
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 93/100·All SAFE
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.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.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
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