Implement ingest-service: MQTT -> ClickHouse (batched) + RabbitMQ event
Subscribes to devices/+/telemetry, validates payloads, buffers writes into ClickHouse (flush on size or interval), and emits a per-reading event on RabbitMQ for rule-engine-service to consume later. Adds the initial Postgres and ClickHouse schema migrations, a Dockerfile, and wires the service into docker-compose. Verified end-to-end via docker compose + mosquitto_pub: row lands in ClickHouse and the event reaches the telemetry.new_reading queue. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS telemetry.telemetry (
|
||||
device_id String,
|
||||
zone_id String,
|
||||
sensor_type String,
|
||||
value Float64,
|
||||
recorded_at DateTime,
|
||||
received_at DateTime
|
||||
) ENGINE = MergeTree()
|
||||
ORDER BY (device_id, recorded_at);
|
||||
@@ -0,0 +1,59 @@
|
||||
-- Core platform schema: users, zones, device types, devices, automation rules.
|
||||
-- Deliberately generic — no growbox-specific tables or columns. A "growbox"
|
||||
-- is just a row in zones with device_types like light/pump/fan attached to it.
|
||||
|
||||
CREATE TABLE users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'owner' CHECK (role IN ('owner', 'viewer')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE zones (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE device_types (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code TEXT NOT NULL UNIQUE, -- light, pump, fan, sensor_temp_humidity, vacuum, ...
|
||||
category TEXT NOT NULL CHECK (category IN ('actuator', 'sensor')),
|
||||
capabilities JSONB NOT NULL DEFAULT '[]' -- e.g. ["turn_on","turn_off","set_level"]
|
||||
);
|
||||
|
||||
CREATE TABLE devices (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
zone_id BIGINT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
|
||||
device_type_id BIGINT NOT NULL REFERENCES device_types(id),
|
||||
name TEXT NOT NULL,
|
||||
external_id TEXT NOT NULL UNIQUE, -- physical device id (ESP32 chip id, etc.)
|
||||
protocol TEXT NOT NULL DEFAULT 'mqtt',
|
||||
status TEXT NOT NULL DEFAULT 'offline' CHECK (status IN ('online', 'offline')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX devices_zone_id_idx ON devices(zone_id);
|
||||
CREATE INDEX devices_external_id_idx ON devices(external_id);
|
||||
|
||||
CREATE TABLE automation_rules (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
zone_id BIGINT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
|
||||
target_device_id BIGINT NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
||||
condition_source_device_id BIGINT NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
||||
condition_sensor_type TEXT NOT NULL,
|
||||
condition_operator TEXT NOT NULL CHECK (condition_operator IN ('>', '<', '>=', '<=', '=', '!=')),
|
||||
condition_value DOUBLE PRECISION NOT NULL,
|
||||
action_type TEXT NOT NULL, -- e.g. turn_on, turn_off, set_level
|
||||
action_params JSONB NOT NULL DEFAULT '{}',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX automation_rules_zone_id_idx ON automation_rules(zone_id);
|
||||
CREATE INDEX automation_rules_active_idx ON automation_rules(is_active) WHERE is_active;
|
||||
Reference in New Issue
Block a user