From e9a66cd1f4e04e023c28b77e3b90405a4c7c14b3 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Fri, 26 Dec 2025 01:44:21 +0100 Subject: [PATCH] u --- README.md | 139 ----------------- debug_db.js | 17 --- implementation_plan.md | 335 ----------------------------------------- nginx_proxy.md | 120 --------------- promptlog.txt | 258 ------------------------------- verify_changelog.js | 48 ------ 6 files changed, 917 deletions(-) delete mode 100644 README.md delete mode 100644 debug_db.js delete mode 100644 implementation_plan.md delete mode 100644 nginx_proxy.md delete mode 100644 promptlog.txt delete mode 100644 verify_changelog.js diff --git a/README.md b/README.md deleted file mode 100644 index c8da157..0000000 --- a/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# TischlerCtrl - Sensor Data Collection System - -A Node.js server that collects sensor data from multiple agents via WebSocket, stores it in SQLite with automatic data summarization and retention policies. - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Central Server (Node.js) │ -│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │ -│ │ WebSocket │ │ SQLite DB │ │ Aggregation & │ │ -│ │ Server │──│ sensor_data │ │ Cleanup Jobs │ │ -│ │ :8080 │ │ sensor_10m │ │ (10m, 1h) │ │ -│ └─────────────┘ │ sensor_1h │ └──────────────────┘ │ -└────────┬─────────┴──────────────────────────────────────────┘ - │ - ┌────┴────┬──────────────┐ - │ │ │ -┌───▼───┐ ┌───▼───┐ ┌─────▼─────┐ -│ AC │ │ Tapo │ │ CLI │ -│Infinity│ │ Agent │ │ Agent │ -│ Agent │ │(Rust) │ │ (bash) │ -└───────┘ └───────┘ └───────────┘ -``` - -## Quick Start - -### 1. Start the Server - -```bash -cd server -cp .env.example .env -npm install -npm start -``` - -### 2. Generate API Keys - -```bash -cd server -node src/cli/generate-key.js "ac-infinity-agent" "ac:" -node src/cli/generate-key.js "tapo-agent" "tapo:" -node src/cli/generate-key.js "custom" "custom:" -``` - -### 3. Configure and Start AC Infinity Agent - -```bash -cd agents/ac-infinity -cp .env.example .env -# Edit .env with your AC Infinity credentials and API key -npm install -npm start -``` - -### 4. Build and Deploy Tapo Agent (Rust) - -```bash -cd agents/tapo -cp config.toml.example config.toml -# Edit config.toml with your Tapo devices and API key - -# Build for local machine -cargo build --release - -# Or cross-compile for Raspberry Pi (requires cross) -# cargo install cross -# cross build --release --target armv7-unknown-linux-gnueabihf - -# Run -./target/release/tapo-agent -# Or: RUST_LOG=info ./target/release/tapo-agent -``` - -### 5. Use CLI Agent - -```bash -# Install websocat (one-time) -cargo install websocat -# Or: sudo apt install websocat - -# Send data -export SENSOR_API_KEY="your-custom-api-key" -export SENSOR_SERVER="ws://localhost:8080" -./agents/cli/sensor-send mydevice temperature 24.5 -``` - -## Data Retention Policy - -| Resolution | Retention | Source | -|------------|-----------|--------| -| Raw (1 min) | 7 days | `sensor_data` | -| 10 minutes | 30 days | `sensor_data_10m` | -| 1 hour | Forever | `sensor_data_1h` | - -Data is averaged when aggregating to higher resolutions. - -## WebSocket Protocol - -### Authentication -```json -→ {"type": "auth", "apiKey": "your-api-key"} -← {"type": "auth", "success": true, "devicePrefix": "ac:"} -``` - -### Send Data -```json -→ {"type": "data", "readings": [ - {"device": "ctrl1", "channel": "temperature", "value": 24.5}, - {"device": "ctrl1", "channel": "humidity", "value": 65.0} - ]} -← {"type": "ack", "count": 2} -``` - -## Project Structure - -``` -tischlerctrl/ -├── server/ # Central data collection server -│ ├── src/ -│ │ ├── index.js # Entry point -│ │ ├── config.js # Configuration -│ │ ├── db/ # Database schema & queries -│ │ ├── websocket/ # WebSocket server -│ │ ├── jobs/ # Aggregation & cleanup jobs -│ │ └── cli/ # CLI tools (generate-key) -│ └── data/ # SQLite database files -│ -├── agents/ -│ ├── ac-infinity/ # Node.js AC Infinity agent -│ ├── tapo/ # Rust Tapo smart plug agent -│ └── cli/ # Bash CLI tool -│ -└── README.md -``` - -## License - -MIT diff --git a/debug_db.js b/debug_db.js deleted file mode 100644 index dcb5eed..0000000 --- a/debug_db.js +++ /dev/null @@ -1,17 +0,0 @@ -const Database = require('better-sqlite3'); -const path = require('path'); - -const dbPath = path.resolve(__dirname, 'server/data/sensors.db'); -const db = new Database(dbPath, { readonly: true }); - -console.log('--- RULES ---'); -const rules = db.prepare('SELECT * FROM rules').all(); -console.log(JSON.stringify(rules, null, 2)); - -console.log('\n--- OUTPUT CHANNELS ---'); -const outputs = db.prepare('SELECT * FROM output_events WHERE channel = "CircFanLevel" ORDER BY timestamp DESC LIMIT 10').all(); -console.table(outputs); - -console.log('\n--- SENSOR DATA (ac:tent:temperature) ---'); -const sensors = db.prepare('SELECT * FROM sensor_events WHERE device = "ac" AND channel = "tent:temperature" ORDER BY timestamp DESC LIMIT 5').all(); -console.table(sensors); diff --git a/implementation_plan.md b/implementation_plan.md deleted file mode 100644 index baa9749..0000000 --- a/implementation_plan.md +++ /dev/null @@ -1,335 +0,0 @@ -# Sensor Data Collection System - -A Node.js server that collects sensor data from multiple agents via WebSocket, stores it in SQLite with automatic data summarization and retention policies. - -## Architecture Overview - -```mermaid -graph TB - subgraph "Central Server (Node.js)" - WS[WebSocket Server :8080] - DB[(SQLite Database)] - AGG[Aggregation Job] - WS --> DB - AGG --> DB - end - - subgraph "AC Infinity Agent (Node.js)" - AC[AC Infinity Client] - AC -->|polls every 60s| ACAPI[AC Infinity Cloud API] - AC -->|WebSocket| WS - end - - subgraph "Tapo Agent (Rust)" - TAPO[Tapo Client] - TAPO -->|polls every 60s| PLUG[Tapo P100/P110] - TAPO -->|WebSocket| WS - end - - subgraph "Custom CLI Agent" - CLI[Shell Script] - CLI -->|WebSocket| WS - end -``` - ---- - -## User Review Required - -> [!IMPORTANT] -> **Tapo Agent Language Choice**: I recommend **Rust** for the Tapo agent because: -> - Compiles to a single ~2MB static binary -> - Uses ~5-10MB RAM at runtime -> - Excellent [tapo crate](https://crates.io/crates/tapo) already exists -> - Easy cross-compilation for Raspberry Pi -> -> Alternatively, I could write it in **Go** (would need to implement protocol from scratch) or as a **Node.js** agent (but you mentioned wanting it lightweight). - -> [!IMPORTANT] -> **AC Infinity Credentials**: The AC Infinity API requires email/password authentication to their cloud service. These will need to be stored in configuration. - ---- - -## Project Structure - -``` -tischlerctrl/ -├── server/ -│ ├── package.json -│ ├── src/ -│ │ ├── index.js # Entry point -│ │ ├── config.js # Configuration loader -│ │ ├── db/ -│ │ │ ├── schema.js # SQLite schema + migrations -│ │ │ └── queries.js # Database operations -│ │ ├── websocket/ -│ │ │ ├── server.js # WebSocket server -│ │ │ └── handlers.js # Message handlers -│ │ └── jobs/ -│ │ ├── aggregator.js # Data summarization job -│ │ └── cleanup.js # Data retention cleanup -│ └── data/ -│ └── sensors.db # SQLite database file -│ -├── agents/ -│ ├── ac-infinity/ -│ │ ├── package.json -│ │ └── src/ -│ │ ├── index.js # Entry point -│ │ ├── config.js # Configuration -│ │ ├── ac-client.js # AC Infinity API client -│ │ └── ws-client.js # WebSocket client with reconnect -│ │ -│ ├── tapo/ -│ │ ├── Cargo.toml -│ │ └── src/ -│ │ └── main.rs # Rust Tapo agent -│ │ -│ └── cli/ -│ └── sensor-send # Shell script CLI tool -│ -├── .env.example # Example environment variables -└── README.md -``` - ---- - -## Proposed Changes - -### Server - Database Schema - -#### [NEW] [schema.js](file:///home/seb/src/tischlerctrl/server/src/db/schema.js) - -SQLite tables: - -```sql --- API keys for agent authentication -CREATE TABLE api_keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key TEXT UNIQUE NOT NULL, - name TEXT NOT NULL, -- e.g., "ac-infinity-agent" - device_prefix TEXT NOT NULL, -- e.g., "ac:" or "tapo:" - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - last_used_at DATETIME -); - --- Raw sensor data (1-minute resolution, kept for 1 week) -CREATE TABLE sensor_data ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME NOT NULL, - device TEXT NOT NULL, -- e.g., "ac:controller-69-grow" - channel TEXT NOT NULL, -- e.g., "temperature", "humidity", "power" - value REAL NOT NULL, - INDEX idx_sensor_data_time (timestamp), - INDEX idx_sensor_data_device (device, channel) -); - --- 10-minute aggregated data (kept for 1 month) -CREATE TABLE sensor_data_10m ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME NOT NULL, -- Rounded to 10-min boundary - device TEXT NOT NULL, - channel TEXT NOT NULL, - value REAL NOT NULL, -- Averaged value - sample_count INTEGER NOT NULL, - UNIQUE(timestamp, device, channel) -); - --- 1-hour aggregated data (kept forever) -CREATE TABLE sensor_data_1h ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME NOT NULL, -- Rounded to 1-hour boundary - device TEXT NOT NULL, - channel TEXT NOT NULL, - value REAL NOT NULL, -- Averaged value - sample_count INTEGER NOT NULL, - UNIQUE(timestamp, device, channel) -); -``` - ---- - -### Server - WebSocket Protocol - -#### [NEW] [server.js](file:///home/seb/src/tischlerctrl/server/src/websocket/server.js) - -**Authentication Flow:** -1. Client connects to `ws://server:8080` -2. Client sends: `{ "type": "auth", "apiKey": "xxx" }` -3. Server validates API key, responds: `{ "type": "auth", "success": true, "devicePrefix": "ac:" }` -4. Client is now authenticated and can send data - -**Data Ingestion Message:** -```json -{ - "type": "data", - "readings": [ - { "device": "controller-69-grow", "channel": "temperature", "value": 24.5 }, - { "device": "controller-69-grow", "channel": "humidity", "value": 65.2 } - ] -} -``` - -Server prepends `devicePrefix` to device names and adds timestamp. - -**Keepalive:** -- Server sends `ping` every 30 seconds -- Client responds with `pong` -- Connection closed after 90 seconds of no response - ---- - -### Server - Aggregation Jobs - -#### [NEW] [aggregator.js](file:///home/seb/src/tischlerctrl/server/src/jobs/aggregator.js) - -Runs every 10 minutes: - -1. **10-minute aggregation**: - - Select data from `sensor_data` older than 10 minutes - - Group by device, channel, and 10-minute bucket - - Calculate average, insert into `sensor_data_10m` - -2. **1-hour aggregation**: - - Select data from `sensor_data_10m` older than 1 hour - - Group by device, channel, and 1-hour bucket - - Calculate weighted average, insert into `sensor_data_1h` - -#### [NEW] [cleanup.js](file:///home/seb/src/tischlerctrl/server/src/jobs/cleanup.js) - -Runs every hour: -- Delete from `sensor_data` where timestamp < NOW - 7 days -- Delete from `sensor_data_10m` where timestamp < NOW - 30 days - ---- - -### AC Infinity Agent - -#### [NEW] [ac-client.js](file:///home/seb/src/tischlerctrl/agents/ac-infinity/src/ac-client.js) - -Port of the TypeScript AC Infinity client to JavaScript ES modules: - -- `login(email, password)` → Returns userId token -- `getDevicesListAll()` → Returns all controllers with sensor readings -- Polling interval: 60 seconds -- Extracts: temperature, humidity, VPD (if available) per controller - -**Data extraction from API response:** -```javascript -// Each device in response has: -// - devId, devName -// - devSettings.temperature (°C * 100) -// - devSettings.humidity (% * 100) -// We normalize and send to server -``` - -#### [NEW] [ws-client.js](file:///home/seb/src/tischlerctrl/agents/ac-infinity/src/ws-client.js) - -WebSocket client with: -- Auto-reconnect with exponential backoff (1s → 2s → 4s → ... → 60s max) -- Authentication on connect -- Heartbeat response -- Message queue during disconnection - ---- - -### Tapo Agent (Rust) - -#### [NEW] [main.rs](file:///home/seb/src/tischlerctrl/agents/tapo/src/main.rs) - -Uses [tapo crate](https://crates.io/crates/tapo) for P100/P110 communication. - -**Features:** -- Configuration via environment variables or TOML file -- WebSocket client with tungstenite crate -- Auto-reconnect with backoff -- Polls devices every 60 seconds - -**Data collected:** -| Device | Channel | Description | -|--------|---------|-------------| -| P100 | `state` | 0 = off, 1 = on | -| P110 | `state` | 0 = off, 1 = on | -| P110 | `power` | Current power in watts | -| P110 | `energy_today` | Energy used today in Wh | - -**Build for Raspberry Pi:** -```bash -# Cross-compile for ARM -cross build --release --target armv7-unknown-linux-gnueabihf -# Binary: ~2MB, runs with ~8MB RAM -``` - ---- - -### Custom CLI Agent - -#### [NEW] [sensor-send](file:///home/seb/src/tischlerctrl/agents/cli/sensor-send) - -A shell script using `websocat` (lightweight WebSocket CLI tool): - -```bash -#!/bin/bash -# Usage: sensor-send --device=mydevice --channel=temp --value=23.5 - -API_KEY="${SENSOR_API_KEY:-}" -SERVER="${SENSOR_SERVER:-ws://localhost:8080}" - -sensor-send mydevice temperature 23.5 -``` - -Requires: `websocat` (single binary, ~3MB, available via cargo or apt) - ---- - -## Configuration Examples - -### Server `.env` -```bash -PORT=8080 -DB_PATH=./data/sensors.db -# Generate API keys via CLI: node src/cli/generate-key.js "ac-infinity" "ac:" -``` - -### AC Infinity Agent `.env` -```bash -SERVER_URL=ws://192.168.1.100:8080 -API_KEY=your-api-key-here -AC_EMAIL=your@email.com -AC_PASSWORD=your-password -POLL_INTERVAL_MS=60000 -``` - -### Tapo Agent `config.toml` -```toml -server_url = "ws://192.168.1.100:8080" -api_key = "your-api-key-here" -poll_interval_secs = 60 - -[[devices]] -ip = "192.168.1.50" -name = "grow-light-plug" -type = "P110" # or "P100" -tapo_email = "your@email.com" -tapo_password = "your-tapo-password" -``` - ---- - -## Verification Plan - -### Automated Tests -1. **Server unit tests**: Database operations, aggregation logic -2. **Integration test**: Start server, connect mock agent, verify data flow -3. **Run commands**: - ```bash - cd server && npm test - cd agents/ac-infinity && npm test - ``` - -### Manual Verification -1. Start server, verify WebSocket accepts connections -2. Send test data via CLI agent, verify it appears in database -3. Wait 10+ minutes, verify aggregation runs and data appears in `sensor_data_10m` -4. Connect AC Infinity agent with real credentials, verify sensor readings -5. Deploy Tapo agent to Raspberry Pi, verify plug data collection diff --git a/nginx_proxy.md b/nginx_proxy.md deleted file mode 100644 index eec07a0..0000000 --- a/nginx_proxy.md +++ /dev/null @@ -1,120 +0,0 @@ -# Setting up Nginx as a Reverse Proxy - -This guide explains how to configure Nginx to act as a reverse proxy for the TischlerCtrl server. This allows you to host the application on standard HTTP/HTTPS ports (80/443) and adds a layer of security. - -## Prerequisites - -- A Linux server (Debian/Ubuntu/Raspberry Pi OS). -- Root or sudo access. -- TischlerCtrl server running on localhost (default port: `8080`). - -## 1. Install Nginx - -If Nginx is not already installed: - -```bash -sudo apt update -sudo apt install nginx -``` - -## 2. Create Configuration File - -Create a new configuration file for the site in `/etc/nginx/sites-available/`. We'll name it `tischlerctrl`. - -```bash -sudo nano /etc/nginx/sites-available/tischlerctrl -``` - -Paste the following configuration using your actual domain name or IP address: - -```nginx -server { - listen 80; - server_name your-domain.com; # Replace with your domain or IP address - - # Access logs - access_log /var/log/nginx/tischlerctrl.access.log; - error_log /var/log/nginx/tischlerctrl.error.log; - - location /agentapi/ { - proxy_pass http://localhost:8080/; # Trailing slash strips /agentapi/ - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - - # Forwarding real client IP - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -### Key Configuration Explained - -- **proxy_pass**: Forwards requests to your Node.js application running on port 8080. -- **WebSocket Support**: These lines are **critical** for TischlerCtrl as it relies on WebSockets for real-time sensor data: - ```nginx - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - ``` - -## 3. Enable the Site - -Create a symbolic link to the `sites-enabled` directory to activate the configuration: - -```bash -sudo ln -s /etc/nginx/sites-available/tischlerctrl /etc/nginx/sites-enabled/ -``` - -## 4. Test and Reload Nginx - -Test the configuration for syntax errors: - -```bash -sudo nginx -t -``` - -If the test is successful (returns `syntax is ok`), reload Nginx: - -```bash -sudo systemctl reload nginx -``` - -## 5. SSL Configuration (Recommended) - -To secure your connection with HTTPS (especially important for authentication), use Certbot to automatically configure a free specific Let's Encrypt SSL certificate. - -```bash -sudo apt install certbot python3-certbot-nginx -sudo certbot --nginx -d your-domain.com -``` - -Certbot will automatically modify your Nginx configuration to force HTTPS redirection and manage the SSL certificates. - -## 6. Update Client Configurations - -Since you are serving the API under `/agentapi/`, you must update your agents' configuration to point to the new URL path. - -### WebSocket URL Format - -- **Old (Direct):** `ws://server-ip:8080` -- **New (Proxy):** `ws://your-domain.com/agentapi/` (or `wss://` if using SSL) - -### Example for Tapo Agent (`config.toml`) - -```toml -server_url = "ws://your-domain.com/agentapi/" -# Or with SSL: -# server_url = "wss://your-domain.com/agentapi/" -``` - -### Example for Environment Variables - -For agents using `.env` files: - -```bash -SENSOR_SERVER="ws://your-domain.com/agentapi/" -``` diff --git a/promptlog.txt b/promptlog.txt deleted file mode 100644 index a71c861..0000000 --- a/promptlog.txt +++ /dev/null @@ -1,258 +0,0 @@ -Sensor Data Collection System -A Node.js server that collects sensor data from multiple agents via WebSocket, stores it in SQLite with automatic data summarization and retention policies. - -Architecture Overview -Custom CLI Agent -Tapo Agent (Rust) -AC Infinity Agent (Node.js) -Central Server (Node.js) -polls every 60s -WebSocket -polls every 60s -WebSocket -WebSocket -WebSocket Server :8080 -SQLite Database -Aggregation Job -AC Infinity Client -AC Infinity Cloud API -Tapo Client -Tapo P100/P110 -Shell Script -User Review Required -IMPORTANT - -Tapo Agent Language Choice: I recommend Rust for the Tapo agent because: - -Compiles to a single ~2MB static binary -Uses ~5-10MB RAM at runtime -Excellent tapo crate already exists -Easy cross-compilation for Raspberry Pi -Alternatively, I could write it in Go (would need to implement protocol from scratch) or as a Node.js agent (but you mentioned wanting it lightweight). - -IMPORTANT - -AC Infinity Credentials: The AC Infinity API requires email/password authentication to their cloud service. These will need to be stored in configuration. - -Project Structure -tischlerctrl/ -├── server/ -│ ├── package.json -│ ├── src/ -│ │ ├── index.js # Entry point -│ │ ├── config.js # Configuration loader -│ │ ├── db/ -│ │ │ ├── schema.js # SQLite schema + migrations -│ │ │ └── queries.js # Database operations -│ │ ├── websocket/ -│ │ │ ├── server.js # WebSocket server -│ │ │ └── handlers.js # Message handlers -│ │ └── jobs/ -│ │ ├── aggregator.js # Data summarization job -│ │ └── cleanup.js # Data retention cleanup -│ └── data/ -│ └── sensors.db # SQLite database file -│ -├── agents/ -│ ├── ac-infinity/ -│ │ ├── package.json -│ │ └── src/ -│ │ ├── index.js # Entry point -│ │ ├── config.js # Configuration -│ │ ├── ac-client.js # AC Infinity API client -│ │ └── ws-client.js # WebSocket client with reconnect -│ │ -│ ├── tapo/ -│ │ ├── Cargo.toml -│ │ └── src/ -│ │ └── main.rs # Rust Tapo agent -│ │ -│ └── cli/ -│ └── sensor-send # Shell script CLI tool -│ -├── .env.example # Example environment variables -└── README.md -Proposed Changes -Server - Database Schema -[NEW] -schema.js -SQLite tables: - --- API keys for agent authentication -CREATE TABLE api_keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key TEXT UNIQUE NOT NULL, - name TEXT NOT NULL, -- e.g., "ac-infinity-agent" - device_prefix TEXT NOT NULL, -- e.g., "ac:" or "tapo:" - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - last_used_at DATETIME -); --- Raw sensor data (1-minute resolution, kept for 1 week) -CREATE TABLE sensor_data ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME NOT NULL, - device TEXT NOT NULL, -- e.g., "ac:controller-69-grow" - channel TEXT NOT NULL, -- e.g., "temperature", "humidity", "power" - value REAL NOT NULL, - INDEX idx_sensor_data_time (timestamp), - INDEX idx_sensor_data_device (device, channel) -); --- 10-minute aggregated data (kept for 1 month) -CREATE TABLE sensor_data_10m ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME NOT NULL, -- Rounded to 10-min boundary - device TEXT NOT NULL, - channel TEXT NOT NULL, - value REAL NOT NULL, -- Averaged value - sample_count INTEGER NOT NULL, - UNIQUE(timestamp, device, channel) -); --- 1-hour aggregated data (kept forever) -CREATE TABLE sensor_data_1h ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME NOT NULL, -- Rounded to 1-hour boundary - device TEXT NOT NULL, - channel TEXT NOT NULL, - value REAL NOT NULL, -- Averaged value - sample_count INTEGER NOT NULL, - UNIQUE(timestamp, device, channel) -); -Server - WebSocket Protocol -[NEW] -server.js -Authentication Flow: - -Client connects to ws://server:8080 -Client sends: { "type": "auth", "apiKey": "xxx" } -Server validates API key, responds: { "type": "auth", "success": true, "devicePrefix": "ac:" } -Client is now authenticated and can send data -Data Ingestion Message: - -{ - "type": "data", - "readings": [ - { "device": "controller-69-grow", "channel": "temperature", "value": 24.5 }, - { "device": "controller-69-grow", "channel": "humidity", "value": 65.2 } - ] -} -Server prepends devicePrefix to device names and adds timestamp. - -Keepalive: - -Server sends ping every 30 seconds -Client responds with pong -Connection closed after 90 seconds of no response -Server - Aggregation Jobs -[NEW] -aggregator.js -Runs every 10 minutes: - -10-minute aggregation: - -Select data from sensor_data older than 10 minutes -Group by device, channel, and 10-minute bucket -Calculate average, insert into sensor_data_10m -1-hour aggregation: - -Select data from sensor_data_10m older than 1 hour -Group by device, channel, and 1-hour bucket -Calculate weighted average, insert into sensor_data_1h -[NEW] -cleanup.js -Runs every hour: - -Delete from sensor_data where timestamp < NOW - 7 days -Delete from sensor_data_10m where timestamp < NOW - 30 days -AC Infinity Agent -[NEW] -ac-client.js -Port of the TypeScript AC Infinity client to JavaScript ES modules: - -login(email, password) → Returns userId token -getDevicesListAll() → Returns all controllers with sensor readings -Polling interval: 60 seconds -Extracts: temperature, humidity, VPD (if available) per controller -Data extraction from API response: - -// Each device in response has: -// - devId, devName -// - devSettings.temperature (°C * 100) -// - devSettings.humidity (% * 100) -// We normalize and send to server -[NEW] -ws-client.js -WebSocket client with: - -Auto-reconnect with exponential backoff (1s → 2s → 4s → ... → 60s max) -Authentication on connect -Heartbeat response -Message queue during disconnection -Tapo Agent (Rust) -[NEW] -main.rs -Uses tapo crate for P100/P110 communication. - -Features: - -Configuration via environment variables or TOML file -WebSocket client with tungstenite crate -Auto-reconnect with backoff -Polls devices every 60 seconds -Data collected: - -Device Channel Description -P100 state 0 = off, 1 = on -P110 state 0 = off, 1 = on -P110 power Current power in watts -P110 energy_today Energy used today in Wh -Build for Raspberry Pi: - -# Cross-compile for ARM -cross build --release --target armv7-unknown-linux-gnueabihf -# Binary: ~2MB, runs with ~8MB RAM -Custom CLI Agent -[NEW] -sensor-send -A shell script using websocat (lightweight WebSocket CLI tool): - -#!/bin/bash -# Usage: sensor-send --device=mydevice --channel=temp --value=23.5 -API_KEY="${SENSOR_API_KEY:-}" -SERVER="${SENSOR_SERVER:-ws://localhost:8080}" -sensor-send mydevice temperature 23.5 -Requires: websocat (single binary, ~3MB, available via cargo or apt) - -Configuration Examples -Server .env -PORT=8080 -DB_PATH=./data/sensors.db -# Generate API keys via CLI: node src/cli/generate-key.js "ac-infinity" "ac:" -AC Infinity Agent .env -SERVER_URL=ws://192.168.1.100:8080 -API_KEY=your-api-key-here -AC_EMAIL=your@email.com -AC_PASSWORD=your-password -POLL_INTERVAL_MS=60000 -Tapo Agent config.toml -server_url = "ws://192.168.1.100:8080" -api_key = "your-api-key-here" -poll_interval_secs = 60 -[[devices]] -ip = "192.168.1.50" -name = "grow-light-plug" -type = "P110" # or "P100" -tapo_email = "your@email.com" -tapo_password = "your-tapo-password" -Verification Plan -Automated Tests -Server unit tests: Database operations, aggregation logic -Integration test: Start server, connect mock agent, verify data flow -Run commands: -cd server && npm test -cd agents/ac-infinity && npm test -Manual Verification -Start server, verify WebSocket accepts connections -Send test data via CLI agent, verify it appears in database -Wait 10+ minutes, verify aggregation runs and data appears in sensor_data_10m -Connect AC Infinity agent with real credentials, verify sensor readings -Deploy Tapo agent to Raspberry Pi, verify plug data collection \ No newline at end of file diff --git a/verify_changelog.js b/verify_changelog.js deleted file mode 100644 index 4ff793d..0000000 --- a/verify_changelog.js +++ /dev/null @@ -1,48 +0,0 @@ -const Database = require('better-sqlite3'); -const path = require('path'); - -const dbPath = path.resolve(__dirname, 'server/data/sensors.db'); -console.log(`Connecting to database at ${dbPath}`); -const db = new Database(dbPath); - -// 1. Verify Table Creation -console.log('Creating changelog table...'); -try { - db.exec(` - CREATE TABLE IF NOT EXISTS changelog ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - date TEXT NOT NULL, - user TEXT, - text TEXT NOT NULL - ) - `); - console.log('PASS: Table creation successful (or already exists)'); -} catch (err) { - console.error('FAIL: Table creation failed:', err.message); - process.exit(1); -} - -// 2. Verify Insert -console.log('Inserting test entry...'); -try { - const stmt = db.prepare('INSERT INTO changelog (date, user, text) VALUES (?, ?, ?)'); - const info = stmt.run(new Date().toISOString(), 'test_user', 'Test changelog entry'); - console.log(`PASS: Insert successful, ID: ${info.lastInsertRowid}`); -} catch (err) { - console.error('FAIL: Insert failed:', err.message); - process.exit(1); -} - -// 3. Verify Read -console.log('Reading entries...'); -try { - const rows = db.prepare('SELECT * FROM changelog ORDER BY id DESC LIMIT 5').all(); - console.table(rows); - if (rows.length > 0 && rows[0].user === 'test_user') { - console.log('PASS: Read verification successful'); - } else { - console.error('FAIL: Read verification failed or data mismatch'); - } -} catch (err) { - console.error('FAIL: Read failed:', err.message); -}