Compare commits
No commits in common. "cc0378a910e695904513c9bdf775c04cba81117f" and "b752a3d048a0f4c3e51de1c6894846a691be2eb2" have entirely different histories.
cc0378a910
...
b752a3d048
273
docs/db.sql
273
docs/db.sql
@ -1,273 +0,0 @@
|
|||||||
-- Postgres database for lamp-control system
|
|
||||||
|
|
||||||
CREATE TABLE users (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
first_name VARCHAR(50),
|
|
||||||
last_name VARCHAR(50),
|
|
||||||
role SMALLINT NOT NULL DEFAULT 0 CHECK (role BETWEEN 0 AND 3), -- 0=viewer, 1=user, 2=operator, 3=administrator
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_login TIMESTAMPTZ,
|
|
||||||
banned_at TIMESTAMPTZ,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE user_sessions (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
||||||
session_token VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
expires_at TIMESTAMPTZ NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE zones (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE lamp_profiles (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
voltage INTEGER NOT NULL, -- mV (millivolts)
|
|
||||||
current INTEGER NOT NULL, -- mA (milliamps)
|
|
||||||
runtime INTEGER NOT NULL, -- minutes
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE sensors (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
device_id VARCHAR(36) UNIQUE,
|
|
||||||
gb_id VARCHAR(36) UNIQUE,
|
|
||||||
latitude DECIMAL(9,6),
|
|
||||||
longitude DECIMAL(9,6),
|
|
||||||
zone_id INT REFERENCES zones(id),
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE lamps_relay_boxes (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
device_id VARCHAR(36) UNIQUE, -- uuid
|
|
||||||
gb_id VARCHAR(36) UNIQUE, -- uuid
|
|
||||||
identification_number VARCHAR(50) UNIQUE,
|
|
||||||
-- TODO: calculate status instead of storing it?
|
|
||||||
status SMALLINT NOT NULL DEFAULT 0 CHECK (status BETWEEN 0 AND 2), -- 0=off, 1=on, 2=error
|
|
||||||
latitude DECIMAL(9,6),
|
|
||||||
longitude DECIMAL(9,6),
|
|
||||||
lamp_post_number VARCHAR(50) NOT NULL,
|
|
||||||
running_minutes INTEGER NOT NULL DEFAULT 0,
|
|
||||||
zone_id INT REFERENCES zones(id),
|
|
||||||
profile_id INT REFERENCES lamp_profiles(id),
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_lamp_relay_boxes_zone_id ON lamp_relay_boxes(zone_id);
|
|
||||||
|
|
||||||
-- TODO: maybe calculate running minutes based on metrics instead of status changes?
|
|
||||||
-- Function to calculate running minutes -- Modified function name and logic
|
|
||||||
CREATE OR REPLACE FUNCTION update_lamp_relay_box_running_minutes()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
BEGIN
|
|
||||||
IF NEW.status = 1 AND OLD.status = 0 THEN
|
|
||||||
UPDATE lamp_relay_boxes
|
|
||||||
SET running_minutes = running_minutes + EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - OLD.updated_at)) / 60
|
|
||||||
WHERE id = NEW.id;
|
|
||||||
END IF;
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
-- Trigger to update running minutes when lamp_relay_box status changes -- Modified trigger name
|
|
||||||
CREATE TRIGGER trg_update_lamp_relay_box_running_minutes
|
|
||||||
AFTER UPDATE OF status ON lamp_relay_boxes
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION trg_update_lamp_relay_box_running_minutes();
|
|
||||||
|
|
||||||
CREATE TABLE lamp_relay_box_logs (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
lamp_relay_box_id INT NOT NULL REFERENCES lamp_relay_boxes(id),
|
|
||||||
status SMALLINT NOT NULL, -- 0=off, 1=on, 2=error
|
|
||||||
event_type SMALLINT NOT NULL, -- 0=manual, 1=scheduled, 2=error, 3=gb
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_lamp_relay_box_logs_lamp_relay_box_id_created_at ON lamp_relay_box_logs(lamp_relay_box_id, created_at);
|
|
||||||
|
|
||||||
CREATE TABLE metrics (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
lamp_relay_box_id INTEGER NOT NULL REFERENCES lamp_relay_boxes(id),
|
|
||||||
metric SMALLINT NOT NULL CHECK (metric BETWEEN 0 AND 6), -- 0=current, 1=voltage, 2=power, 3=energy, 4=lux, 5=brightness, 6=motions
|
|
||||||
value INTEGER NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_metrics_lamp_relay_box_id_metric_created_at ON metrics(lamp_relay_box_id, metric, created_at);
|
|
||||||
|
|
||||||
-- Create a view for real-time lamp_relay_box status
|
|
||||||
CREATE VIEW real_time_lamp_relay_box_status AS
|
|
||||||
SELECT
|
|
||||||
l.id,
|
|
||||||
l.identification_number,
|
|
||||||
l.status, -- TODO: calculate status instead of storing it?
|
|
||||||
l.latitude,
|
|
||||||
l.longitude,
|
|
||||||
lux.value AS lux,
|
|
||||||
motions.value AS motions,
|
|
||||||
power.value AS power,
|
|
||||||
current.value AS current,
|
|
||||||
voltage.value AS voltage,
|
|
||||||
energy.value AS energy,
|
|
||||||
brightness.value AS brightness,
|
|
||||||
GREATEST(lux.created_at, motions.created_at, power.created_at, current.created_at, voltage.created_at, energy.created_at, brightness.created_at) AS last_updated
|
|
||||||
FROM lamp_relay_boxes l
|
|
||||||
LEFT JOIN LATERAL (
|
|
||||||
SELECT value, created_at
|
|
||||||
FROM metrics
|
|
||||||
WHERE lamp_relay_box_id = l.id
|
|
||||||
AND metric = 0 -- current
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
) current ON true
|
|
||||||
LEFT JOIN LATERAL (
|
|
||||||
SELECT value, created_at
|
|
||||||
FROM metrics
|
|
||||||
WHERE lamp_relay_box_id = l.id
|
|
||||||
AND metric = 1 -- voltage
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
) voltage ON true
|
|
||||||
LEFT JOIN LATERAL (
|
|
||||||
SELECT value, created_at
|
|
||||||
FROM metrics
|
|
||||||
WHERE lamp_relay_box_id = l.id
|
|
||||||
AND metric = 2 -- power
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
) power ON true
|
|
||||||
LEFT JOIN LATERAL (
|
|
||||||
SELECT value, created_at
|
|
||||||
FROM metrics
|
|
||||||
WHERE lamp_relay_box_id = l.id
|
|
||||||
AND metric = 3 -- energy
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
) energy ON true
|
|
||||||
LEFT JOIN LATERAL (
|
|
||||||
SELECT value, created_at
|
|
||||||
FROM metrics
|
|
||||||
WHERE lamp_relay_box_id = l.id
|
|
||||||
AND metric = 4 -- lux
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
) lux ON true
|
|
||||||
LEFT JOIN LATERAL (
|
|
||||||
SELECT value, created_at
|
|
||||||
FROM metrics
|
|
||||||
WHERE lamp_relay_box_id = l.id
|
|
||||||
AND metric = 5 -- brightness
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
) brightness ON true
|
|
||||||
LEFT JOIN LATERAL (
|
|
||||||
SELECT value, created_at
|
|
||||||
FROM metrics
|
|
||||||
WHERE lamp_relay_box_id = l.id
|
|
||||||
AND metric = 6 -- motions
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
) motions ON true;
|
|
||||||
|
|
||||||
-- Function to update lamp_relay_box status based on metrics
|
|
||||||
CREATE OR REPLACE FUNCTION update_lamp_relay_box_status()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
BEGIN
|
|
||||||
UPDATE lamp_relay_boxes
|
|
||||||
SET
|
|
||||||
status = CASE
|
|
||||||
WHEN NEW.power_watts > 0 THEN 1
|
|
||||||
ELSE 0
|
|
||||||
END,
|
|
||||||
updated_at = NEW.created_at
|
|
||||||
WHERE id = NEW.lamp_relay_box_id;
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
-- Trigger to update lamp_relay_box status when new metrics is inserted
|
|
||||||
CREATE TRIGGER trigger_update_lamp_relay_box_status
|
|
||||||
AFTER INSERT ON metrics
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION update_lamp_relay_box_status();
|
|
||||||
|
|
||||||
CREATE TABLE schedules (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
type SMALLINT NOT NULL, -- 0=manual, 1=scheduled
|
|
||||||
start_time TIME NOT NULL,
|
|
||||||
end_time TIME NOT NULL,
|
|
||||||
status SMALLINT NOT NULL, -- 0=on, 1=off
|
|
||||||
days_of_week bit(7) NOT NULL, -- Bitmask for days of the week
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE zone_schedules (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
zone_id INT NOT NULL REFERENCES zones(id),
|
|
||||||
schedule_id INTEGER REFERENCES schedules(id),
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_zone_schedules_zone_id ON zone_schedules(zone_id);
|
|
||||||
|
|
||||||
CREATE TABLE manual_overrides (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
zone_id INT NOT NULL REFERENCES zones(id),
|
|
||||||
user_id INT NOT NULL REFERENCES users(id),
|
|
||||||
start_time TIMESTAMPTZ NOT NULL,
|
|
||||||
end_time TIMESTAMPTZ NOT NULL,
|
|
||||||
type SMALLINT NOT NULL, -- 0=turn_off, 1=turn_on
|
|
||||||
reason VARCHAR(500),
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_manual_overrides_zone_id_start_time ON manual_overrides(zone_id, start_time);
|
|
||||||
|
|
||||||
CREATE TABLE alerts (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
lamp_relay_box_id INT NOT NULL REFERENCES lamp_relay_boxes(id),
|
|
||||||
type SMALLINT NOT NULL,
|
|
||||||
description VARCHAR(500),
|
|
||||||
status SMALLINT NOT NULL DEFAULT 0, -- 0=active, 1=acknowledged, 2=resolved
|
|
||||||
acknowledged_by INTEGER REFERENCES users(user_id),
|
|
||||||
acknowledged_at TIMESTAMPTZ,
|
|
||||||
resolved_by INTEGER REFERENCES users(user_id),
|
|
||||||
resolved_at TIMESTAMPTZ,
|
|
||||||
comments TEXT,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE maintenance_logs (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
lamp_relay_box_id INTEGER REFERENCES lamp_relay_boxes(lamp_relay_box_id),
|
|
||||||
user_id INTEGER REFERENCES users(user_id),
|
|
||||||
action_type SMALLINT NOT NULL, -- 0=inspection, 1=repair, 2=replacement
|
|
||||||
description TEXT,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Index for querying by lamp_relay_box in chronological order
|
|
||||||
CREATE INDEX idx_maintenance_logs_lamp_relay_box_id_created_at ON maintenance_logs(lamp_relay_box_id, created_at);
|
|
||||||
|
|
||||||
-- Index for querying by user in chronological order
|
|
||||||
CREATE INDEX idx_maintenance_logs_user_id_created_at ON maintenance_logs(user_id, created_at);
|
|
||||||
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
|||||||
# Gardens by the Bay - Light-control system
|
|
||||||
|
|
||||||
## Dictionary
|
|
||||||
|
|
||||||
- [**Gardens by the Bay**](https://maps.app.goo.gl/tGZcwxjbVSdzrRBi7): A nature park in Singapore that is home to a variety of plants and flowers. It is a popular tourist attraction and is known for its beautiful gardens and unique architecture.
|
|
||||||
|
|
||||||
- **Light-control system**: A system that controls the lighting of a specific area. Mostly it's about turning on and off the lights, like lamp posts.
|
|
||||||
|
|
||||||
- **Lamp**: A device that produces light. It is usually made of a bulb and a lampshade.
|
|
||||||
|
|
||||||
- **Motion sensor**: A device that detects movement and triggers an action, such as turning on a light.
|
|
||||||
|
|
||||||
- **Lamp post**: A tall post that has a light at the top. It is used to light up streets, parks, and other outdoor areas. A lamp post can have multiple lamps, motion sensors, and other devices attached to it.
|
|
||||||
|
|
||||||
- **Relay box**: A device that controls multiple lamps. It can turn the lamps on and off, dim them, and monitor their status. It can also collect data from sensors attached to the lamps.
|
|
||||||
|
|
||||||
- **Relay box status**: The current state of a lamps, such as on, off or malfunctioning.
|
|
||||||
|
|
||||||
- **Zone**: A specific area that is contains multiple relay boxes. For example, a park can be divided into multiple zones, each with its own set of relay boxes.
|
|
||||||
|
|
||||||
- **Schedule**: A plan that shows when events or tasks will happen. It can be used to schedule the turning on and off of groups of lamps at specific times.
|
|
||||||
|
|
||||||
- **Manual override**: A feature that allows a user to manually control a system, even if it is automated. For example, a user can manually turn on or off a lamp, even if it is scheduled to turn on or off at a specific time. Used for special events.
|
|
||||||
|
|
||||||
- **Role**: A set of permissions that define what a user can do in the system. For example, an admin role can create users and change system settings, while a user role can only view and control lamps.
|
|
||||||
|
|
||||||
- **Viewer Role**: A role that allows a user to view the system but not make any changes. For example, a viewer can see the status of lamps and view schedules, but cannot change the schedule or turn on or off lamps.
|
|
||||||
|
|
||||||
- **User Role**: A role that allows a user to maintain the system. For example, a user can receive notifications about failures, acknowledge them, and fix them. User can download exports of the data and configure manual bypasses.
|
|
||||||
|
|
||||||
- **Operator Role**: A role that allows a user to control the system. For example, an operator can turn on and off lamps, create schedules, add or remove devices.
|
|
||||||
|
|
||||||
- **Admin Role**: A role that allows a user to manage the system. For example, an admin can create users, assign roles, and change system settings.
|
|
||||||
|
|
||||||
- **2FA**: Two-factor authentication. A security feature that requires two forms of identification to sign in to a system. For example, a user may need to enter a password and a code from their OTP authenticator app to sign in.
|
|
||||||
|
|
||||||
- **Dashboard**: A visual representation of data. It can show the status of lamps, schedules, and other system information. It can also show graphs and charts of data collected from the system.
|
|
||||||
|
|
||||||
- **PWA**: Progressive Web App. A type of web application that is designed to work on mobile devices. It can be installed on a device and used offline.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
- User is created by the Admin
|
|
||||||
- User can sign in and sign out using username and password.
|
|
||||||
- User have to use 2FA to sign in.
|
|
||||||
- User can change password in their profile settings page.
|
|
||||||
|
|
||||||
### User management
|
|
||||||
|
|
||||||
- Admin can create, update, delete users.
|
|
||||||
- TODO
|
|
||||||
|
|
||||||
### Realtime Dashboard
|
|
||||||
|
|
||||||
### Lamp management
|
|
||||||
|
|
||||||
- Lamps are grouped into packs of max 5 lamps. They are controlled together by a Relay box. The relay box electrically connects the lamps to the power source and can turn them on and off.
|
|
||||||
- Lamps can be of different lamp profiles - depends on their rated voltage, current, and power.
|
|
||||||
- Relay box can contain lamps of multiple lamp profiles.
|
|
||||||
- All relay boxes should be depicted on a map (like Google Maps), with lamp identifiers, status and realtime updates. In addition to map, there should be a list of all relay boxes.
|
|
||||||
- Relay boxes can have three statuses: Green for online, Yellow for offline according to schedule but operational, Red for malfunctioning, not operating as per schedule due to network issues or other reasons.
|
|
||||||
- Interval of relay boxes status update is 5 minutes.
|
|
||||||
- Metrics collected from relay boxes and nearby sensors are:
|
|
||||||
- Voltage
|
|
||||||
- Current
|
|
||||||
- Power
|
|
||||||
- Running hours (to be calculated from the timestamp of the last status update)
|
|
||||||
- Lux level
|
|
||||||
- Motion detection: 0 or 1 and timestamp of last trigger
|
|
||||||
- Brightness level: 0-100% for dimmable lamps
|
|
||||||
- Operator can add relay boxes, lamps, and sensors to the system. They can also update and delete them. Accurate GPS coordinates are required for each relay box and sensor.
|
|
||||||
- System should collect and store metrics for historical analysis.
|
|
||||||
|
|
||||||
### Data visualization and correlation
|
|
||||||
|
|
||||||
- Dashboard should show the status of all relay boxes and lamps in real-time. It should also show the metrics collected from the relay boxes and sensors as simple 2d graphs.
|
|
||||||
- Data should be downloadable in CSV or Excel format.
|
|
||||||
- Charts and data exports should be available per relay box, zone or globally.
|
|
||||||
- Charts should be able to show comparison of metrics between similar time periods.
|
|
||||||
|
|
||||||
### PWA mobile app
|
|
||||||
|
|
||||||
- Mobile friendly
|
|
||||||
- Push notifications
|
|
Loading…
Reference in New Issue
Block a user