home-sensor-dashboard

7 stars

Best use case

home-sensor-dashboard is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Teams using home-sensor-dashboard should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/home-sensor-dashboard/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/hardware-iot/home-sensor-dashboard/skills/home-sensor-dashboard/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/home-sensor-dashboard/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How home-sensor-dashboard Compares

Feature / Agenthome-sensor-dashboardStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

This skill provides specific capabilities for your AI agent. See the About section for full details.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Skill: home-sensor-dashboard

## What this skill knows

This skill covers the home-sensor-dashboard application: a pnpm-workspaces monorepo with a Node.js/Express/SQLite backend and a React/Vite frontend. It covers the ingest API, alert threshold computation, polling hook, React store shape, and component architecture.

## Monorepo layout

```
home-sensor-dashboard/
  packages/
    server/src/
      index.ts          - Express entry + migrations
      db.ts             - better-sqlite3 singleton
      routes/
        ingest.ts       - POST /api/ingest
        status.ts       - GET /api/status
        sensors.ts      - GET /api/sensors, PUT /api/sensors/:id
        readings.ts     - GET /api/readings/:sensorId
    client/src/
      components/
        SensorCard/     - gauge + sparkline + badge
        SensorGrid/     - responsive CSS grid
        ReadingsChart/  - full Chart.js line chart
        StatusTable/    - sensor list table
        AlertBadge/     - ok/warn/alert/stale pill
      pages/
        DashboardPage.tsx
        SensorDetailPage.tsx
      store/useDashboardStore.ts
      hooks/usePolling.ts
```

## Environment variables

```
PORT=3001
DB_PATH=./data/sensors.db
ALLOWED_ORIGIN=http://localhost:5173
```

## API routes

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/ingest` | Single or array of readings |
| GET | `/api/status` | Latest reading + alert_state per sensor |
| GET | `/api/sensors` | All sensors with metadata |
| PUT | `/api/sensors/:id` | Update name/thresholds |
| GET | `/api/readings/:id` | History with limit/from/to params |

## Ingest endpoint

Accepts a single object or an array:

```json
{ "sensor_id": "bedroom-temp", "value": 22.4, "unit": "C" }
```

```json
[
  { "sensor_id": "bedroom-temp", "value": 22.4, "unit": "C" },
  { "sensor_id": "bedroom-hum",  "value": 61,   "unit": "%" }
]
```

Returns `201 { ok: true, count: N }`.

## DB schema

```sql
CREATE TABLE IF NOT EXISTS sensors (
  id          TEXT PRIMARY KEY,
  name        TEXT NOT NULL DEFAULT '',
  unit        TEXT NOT NULL DEFAULT '',
  alert_min   REAL,
  alert_max   REAL,
  created_at  INTEGER NOT NULL,
  last_seen   INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS readings (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  sensor_id   TEXT NOT NULL REFERENCES sensors(id),
  value       REAL NOT NULL,
  unit        TEXT NOT NULL,
  ts          INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_readings_sensor_ts ON readings (sensor_id, ts DESC);
```

## db.ts pattern

```ts
import Database from 'better-sqlite3';
const db = new Database(process.env.DB_PATH ?? './data/sensors.db');
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
export default db;
```

Always use WAL mode for better concurrent read performance.

## Alert state computation

```ts
function computeAlertState(value: number, min: number | null, max: number | null): 'ok' | 'warn' | 'alert' {
  if (min === null && max === null) return 'ok';
  if ((min !== null && value < min) || (max !== null && value > max)) return 'alert';
  // WARN = within 10% of threshold
  const warnMin = min !== null ? min * 1.1 : null;
  const warnMax = max !== null ? max * 0.9 : null;
  if ((warnMin !== null && value < warnMin) || (warnMax !== null && value > warnMax)) return 'warn';
  return 'ok';
}
```

## Stale sensor detection

A sensor is stale when `Date.now() - last_seen > STALE_THRESHOLD_MS`.
Default: 60000ms (60 seconds). Controlled by the `STALE_THRESHOLD` env var (value in seconds).

The client checks staleness on each poll cycle:

```ts
const STALE_MS = 60_000;
const isStale = (sensor: SensorStatus) => Date.now() - sensor.ts > STALE_MS;
```

Stale sensors render with `opacity: 0.6` and show `badge-stale` class.

## usePolling hook

```ts
export function usePolling<T>(
  url: string,
  onData: (data: T) => void,
  intervalMs: number = 10000
) {
  useEffect(() => {
    let active = true;
    async function run() {
      try {
        const res = await fetch(url);
        if (res.ok && active) onData(await res.json());
      } catch { /* ignore network errors silently */ }
    }
    run();
    const id = setInterval(run, intervalMs);
    return () => { active = false; clearInterval(id); };
  }, [url, intervalMs]);
}
```

## Zustand store

```ts
interface DashboardStore {
  sensors: SensorStatus[];
  selectedSensorId: string | null;
  readings: Record<string, Reading[]>;
  setSensors(sensors: SensorStatus[]): void;
  setSelectedSensor(id: string | null): void;
  setReadings(sensorId: string, readings: Reading[]): void;
}
```

## SensorCard sparkline

The sparkline is a mini Chart.js `line` chart with:
- `animation: false`
- no axes, no legend, no title
- `pointRadius: 0`
- `borderColor: '#d97706'`
- height: 36px

When alert state is `alert`, use `borderColor: '#dc2626'`.

## ReadingsChart

Full-size Chart.js line chart on the detail page:
- x-axis: ISO timestamp labels
- y-axis: auto-scale
- `animation: false`
- gradient fill from `rgba(217,119,6,0.2)` to transparent
- Threshold lines using Chart.js annotation plugin (or custom horizontal line plugin)

## StatusTable columns

| Column | Field | Notes |
|--------|-------|-------|
| Sensor | `sensor.name` or `sensor.id` | Link to detail page |
| Value | `sensor.value` | Right-aligned monospace bold |
| Unit | `sensor.unit` | Secondary text |
| Last Seen | `sensor.ts` | Relative time e.g. "3s ago" |
| Status | `sensor.alert_state` | AlertBadge component |

## Ingest transaction pattern

Use a better-sqlite3 transaction for batch inserts:

```ts
const insertMany = db.transaction((items: IngestItem[]) => {
  for (const item of items) {
    upsertSensor.run(item.sensor_id, item.sensor_id, item.unit, now, now);
    insertReading.run(item.sensor_id, item.value, item.unit, now);
  }
});
insertMany(items);
```

The `ON CONFLICT(id) DO UPDATE` pattern (upsert) updates `last_seen` and `unit` without changing other columns.

## Common mistakes

- Using `async` with better-sqlite3 - all its methods are synchronous. Never use `await db.prepare(...)` or `await stmt.run(...)`.
- Not creating the `data/` directory before opening the database. Check/create with `fs.mkdirSync(path.dirname(DB_PATH), { recursive: true })`.
- Querying without the index: always filter by `sensor_id` first, then order by `ts DESC LIMIT N`. This uses the composite index.
- Polling hook running multiple fetches concurrently: use the `active` flag and `clearInterval` to avoid state updates after unmount.
- Using the sensor `id` field as the display name - always prefer `name` (which defaults to `id` on first registration).

Related Skills

vitals-dashboard

7
from heldernoid/agentic-build-templates

Track blood pressure, heart rate, weight, blood glucose, and temperature over time with target range bands, trend analysis, alerts for out-of-range readings, correlation analysis, and CSV/PDF export. Use when a user needs to log vital sign readings, review trends, acknowledge alerts, or produce a health summary report.

time-series-dashboard

7
from heldernoid/agentic-build-templates

Manage dashboards, metrics, alert rules, and query time-series data in the time-series-dashboard application. Use this skill for all operations against the running server.

pomodoro-dashboard

7
from heldernoid/agentic-build-templates

Browser-only Pomodoro timer SPA with task tracking, focus stats, and a 12-week activity heatmap. All data stored in localStorage. No backend required.

weather-station-dashboard

7
from heldernoid/agentic-build-templates

Query live and historical weather sensor data, manage stations and alert thresholds, and retrieve daily summaries. Use when asked to check current temperature, view recent readings for a station, find the highest temperature recorded this week, dismiss an active alert, list which stations are online, or retrieve today's daily summary. Triggers include "current temperature", "sensor readings", "station status", "alert history", "daily summary", "wind speed", "humidity", "rainfall total", or any query about live or historical weather data.

sensor-ingest

7
from heldernoid/agentic-build-templates

Configure and troubleshoot sensor data ingest into weather-station-dashboard via MQTT or HTTP POST. Use when asked to set up MQTT broker connection, debug why readings are not appearing, configure a new sensor sending data over HTTP, check MQTT topic configuration, test sensor connectivity, or understand the ingest pipeline. Triggers include "MQTT not receiving", "sensor not sending", "configure MQTT", "HTTP POST reading", "ingest setup", "topic configuration", "reading not showing up", or any task involving getting sensor data into the system.

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".