A live dashboard and Discord bot that watch every light and fan in a 3-room office, so nobody's boss has to ask "did someone leave the fan on again?" ever again.
Built for Techathon Nationals & Rover Summit (challenge: "Lights, Fans, Discord: The Boss's Big Idea"), hosted by IUT Robotics Society.
- Overview
- Features
- Architecture
- Tech Stack
- Getting Started
- Environment Variables
- API Reference
- Discord Commands
- Hardware / Circuit Concept
- Project Structure
- Known Limitations
- Testing
- Demo
- License
The office has 3 rooms β a Drawing Room and two Work Rooms β each with 2 fans and 3 lights (15 devices total). People forget to turn things off when they leave, the electricity bill climbs, and nobody notices until it's too late.
This system gives the whole office one live, always-accurate picture of every device's state and power draw, accessible two ways:
- π₯οΈ A real-time web dashboard β live status, power meter, and anomaly alerts, updating without a page refresh
- π€ A Discord bot β the same live data, answerable on demand from wherever the team already talks
Both interfaces read from one shared backend, so they can never show conflicting information.
- β Live status for all 15 devices, grouped by room, updating in real time over WebSocket
- β Total + per-room power consumption meter (Watts) and an estimated daily kWh counter
- β Automatic alerts for devices left on after office hours (9 AMβ5 PM) and rooms left fully on for 2+ continuous hours
- β
Discord bot (
!status,!room <name>,!usage) with humanized, friendly replies β never a raw data dump - β Proactive Discord alert notifications pushed to a designated channel
- β Realistic, dynamic dummy data β no physical hardware required to run or judge this project
- β Graceful degradation everywhere a network boundary exists: WebSocket reconnect-with-backoff, Discord gateway auto-reconnect, and input validation on every route
One backend process (Node.js) owns the only copy of device state. A software simulator drives realistic on/off and power-draw changes over time; a derived alert engine watches for anomalies; a WebSocket hub pushes live updates to the dashboard; and the Discord bot reads the exact same SQLite-backed store through a direct function call β not a network request β so the two interfaces are structurally incapable of disagreeing.
Full diagrams:
diagrams/System_Architecture_Diagram.svgβ vector versiondiagrams/System_Architecture_Diagram.mdβ text/markdown version
Full design rationale, data model, API contracts, and edge-case handling: docs/ARCHITECTURE.md.
| Layer | Technology |
|---|---|
| Backend | Node.js, Express |
| Real-time transport | WebSocket (ws) |
| Discord bot | discord.js β runs in the same process as the backend |
| Dashboard | React |
| Data store | Local SQLite file (node:sqlite) behind one store API, no separate database service |
| Hardware concept | ESP32 + relays + ACS712 current sensor (Wokwi simulation β see below) |
- Node.js 22+
- A Discord bot token (Discord Developer Portal) with the
MESSAGE CONTENTintent enabled - A Discord server where you can add the bot and designate an alerts channel
git clone <this-repo-url>
cd officewatch
# backend (includes the Discord bot β same process, not a separate install)
cd backend
npm install
# dashboard
cd ../dashboard
npm installCopy the example environment file and fill in your values:
cd backend
cp .env.example .envSee Environment Variables below for what each value does.
# from /backend β starts REST API, WebSocket hub, simulator, alert engine,
# and the Discord bot together, in one process
npm startThe backend listens on PORT (default 3000).
It also creates or reuses a local SQLite database at backend/data/officewatch.sqlite by default.
On a healthy startup, the bot logs Discord bot ready as <bot-tag> once Discord authentication completes. If startup fails with EADDRINUSE on 127.0.0.1:3000, stop the existing process on that port or change PORT in backend/.env before retrying.
cd dashboard
npm run devOpen the printed local URL in your browser. The dashboard connects to the backend's /ws endpoint automatically and requests /api/status as a fallback if the socket hasn't connected within a few seconds.
All variables are read by the backend only (/backend/.env) β the dashboard has none, since it only ever talks to the backend's public HTTP/WS surface.
| Variable | Required | Default | Description |
|---|---|---|---|
DISCORD_BOT_TOKEN |
β | β | Bot token from the Discord Developer Portal |
DISCORD_ALERT_CHANNEL_ID |
β | β | Channel ID where proactive alerts get posted |
HOST |
No | 127.0.0.1 |
Bind host for the backend server. Set 0.0.0.0 when you want LAN/container access. |
PORT |
No | 3000 |
Backend HTTP/WS port |
TICK_INTERVAL_MS |
No | 5000 |
Simulator tick interval |
SQLITE_PATH |
No | ./data/officewatch.sqlite |
SQLite database path for persisted device state and usage counters. Use :memory: for ephemeral test or demo runs. |
NODE_ENV |
No | β | Set to production for a real deploy. Does not by itself control the debug route β see below. |
ENABLE_DEBUG_ROUTES |
No | false |
Must be explicitly set to true to enable /api/debug/force-alert. This is the only switch for that route β it is independent of NODE_ENV, so a production deploy is safe by default and a demo box can opt in explicitly without also having to run in dev mode. |
Never commit a real .env file β it's excluded via .gitignore.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/status |
Full state of all 15 devices, grouped by room |
GET |
/api/room/:roomId |
Status of one room (drawing, work1, work2) |
GET |
/api/usage |
Current total wattage + today's estimated kWh |
GET |
/api/alerts |
Active and recently resolved alerts |
GET |
/api/health |
Backend + simulator liveness check |
GET |
/api/docs |
Human-readable backend API reference page |
GET |
/api/docs.json |
OpenAPI 3.1 document for the backend contract |
WS |
/ws |
Live push: full-state, state-update, alert-new, alert-resolved |
POST |
/api/debug/force-alert |
(gated by ENABLE_DEBUG_ROUTES=true) Manufactures a real alert on demand β useful for demo recording, returns 404 otherwise |
GET/POST |
/api/debug/simulation |
(gated by ENABLE_DEBUG_ROUTES=true) Dedicated demo simulation controls: pause/resume, preset scenarios, manual tick, and clock override |
Full contract details (request/response shapes, validation rules, error handling): docs/ARCHITECTURE.md Β§5.
Implementation-specific backend docs live in backend/API_DOCUMENTATION.md.
For presentations, set ENABLE_DEBUG_ROUTES=true and use /api/debug/simulation to load stable scenarios like:
after-hours-alertprolonged-on-alertmixed-anomalyall-offbaseline
You can also pause the simulator, step one tick at a time, and override the effective local time so office-hours logic can be demonstrated on demand.
| Command | Example | What it does |
|---|---|---|
!status |
!status or @OfficeWatch any fan running? |
Summary of all 3 rooms in one message |
!room <name> |
!room work1 or @OfficeWatch status of Work Room 1 |
Status of a specific room β tolerant of natural phrasing |
!usage |
!usage or @OfficeWatch how much power are we using? |
Current total wattage + today's estimated kWh |
Responses are humanized sentences, not raw data dumps β the bot always replies with something useful even if a command is malformed. The bot supports both bang commands and direct @OfficeWatch mention-style questions for the same core actions.
The conversational layer stays grounded in the live backend data: it phrases replies more naturally, focuses on a single room when your message names one, and falls back to a short helpful suggestion instead of silently ignoring unclear prompts.
Quick verification after startup:
- Confirm the backend logs
Discord bot ready as <bot-tag> - Run
!status - Run
!room Work Room 1 - Run
!usage
No physical hardware is required to run or judge this project β all device data is realistically simulated in software. For the required circuit schematic, this project designs (in Wokwi) a representative wiring concept for one room, showing how an ESP32 would read manual toggle switches, drive relays for real fans/lights, and sense actual current draw via an ACS712 sensor.
diagrams/Circuit_Wiring_Diagram.svg/.mdβ wiring reference and pin mappingdiagrams/Component_List.mdβ full bill of materials- Live Wokwi project: https://wokwi.com/projects/468599586267266049
This schematic is a concept artifact. The deployed demo's live data comes entirely from the software simulator described in docs/ARCHITECTURE.md Β§4 β no physical or virtual hardware is required to run this project.
.
βββ backend/
β βββ src/
β βββ config/officeLayout.js # rooms, device counts, aliases, tick interval β single source of constants
β βββ deviceStore.js # the one writer function + all reads, backed by SQLite persistence
β βββ simulator.js # generates live device data
β βββ alertEngine.js # derives anomalies from device state
β βββ routes/ # REST endpoints
β βββ ws/hub.js # WebSocket broadcast
β βββ discord/ # bot client + commands (same process as the API)
β βββ server.js # single entrypoint
βββ dashboard/
β βββ src/ # React app
βββ diagrams/ # all system + circuit diagrams, component list
βββ docs/
β βββ ARCHITECTURE.md # full design documentation
β βββ FRONTEND_BUILD_INSTRUCTIONS.md # dashboard build contract
βββ AGENTS.md # rules followed during AI-assisted development
βββ .gitignore
Documented explicitly rather than left for a reviewer to discover:
- No authentication on the API/WebSocket/dashboard β acceptable for a demo, would be a first addition for production use.
- Recent resolved alerts and Discord notification cooldowns are recalculated after a backend restart; only device state and usage counters are persisted.
- Daylight Saving Time transitions aren't specially handled in the "2 hours continuously on" alert math.
- One backend process handles everything by design, for guaranteed data consistency between the dashboard and bot β a production deployment at real scale would separate these with a shared data layer.
Full list with reasoning: docs/ARCHITECTURE.md Β§11.
cd backend
npm testCovers the device store's write validation, alert-engine boundary conditions (e.g., 1h59m vs. 2h01m of continuous "on" time), and REST route input validation. See docs/ARCHITECTURE.md Β§10 for the full manual + automated validation plan, including WebSocket reconnect and Discord bot resilience checks.
πΉ Video walkthrough: <add your video link here>
<add your chosen license here β e.g. MIT>
Built for Techathon Nationals & Rover Summit by IUT Robotics Society.