Compare commits
2 commits
b57d9b7875
...
0f79e7d121
Author | SHA1 | Date | |
---|---|---|---|
0f79e7d121 | |||
b8b0f2d6a1 |
13 changed files with 321 additions and 222 deletions
|
@ -1,19 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"age" integer,
|
||||
"username" text NOT NULL,
|
||||
"password_hash" text NOT NULL,
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
43
drizzle/20250614083214_lucia.sql
Normal file
43
drizzle/20250614083214_lucia.sql
Normal file
|
@ -0,0 +1,43 @@
|
|||
CREATE TABLE "floors" (
|
||||
"floor" integer PRIMARY KEY NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"image" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "plans" (
|
||||
"floor" integer PRIMARY KEY NOT NULL,
|
||||
"plan" json NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sensor_data" (
|
||||
"uuid" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"sensor" text NOT NULL,
|
||||
"temperature" real NOT NULL,
|
||||
"humidity" real NOT NULL,
|
||||
"pressure" real NOT NULL,
|
||||
"altitude" real NOT NULL,
|
||||
"time" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sensors" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sessions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"age" integer,
|
||||
"username" text NOT NULL,
|
||||
"password_hash" text NOT NULL,
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "sensor_data" ADD CONSTRAINT "sensor_data_sensor_sensors_id_fk" FOREIGN KEY ("sensor") REFERENCES "public"."sensors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "sensors" ADD CONSTRAINT "sensors_user_users_id_fk" FOREIGN KEY ("user") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
|
|
@ -1,103 +0,0 @@
|
|||
{
|
||||
"id": "4b3474a3-7de5-4b99-878e-f839b53c52f7",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"sessions_user_id_users_id_fk": {
|
||||
"name": "sessions_user_id_users_id_fk",
|
||||
"tableFrom": "sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"age": {
|
||||
"name": "age",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"password_hash": {
|
||||
"name": "password_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["username"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
261
drizzle/meta/20250614083214_snapshot.json
Normal file
261
drizzle/meta/20250614083214_snapshot.json
Normal file
|
@ -0,0 +1,261 @@
|
|||
{
|
||||
"id": "23f11c9d-f98b-4321-aca4-54ec7fc4eece",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.floors": {
|
||||
"name": "floors",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"floor": {
|
||||
"name": "floor",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"url": {
|
||||
"name": "url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.plans": {
|
||||
"name": "plans",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"floor": {
|
||||
"name": "floor",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"plan": {
|
||||
"name": "plan",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.sensor_data": {
|
||||
"name": "sensor_data",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"uuid": {
|
||||
"name": "uuid",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"sensor": {
|
||||
"name": "sensor",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"temperature": {
|
||||
"name": "temperature",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"humidity": {
|
||||
"name": "humidity",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"pressure": {
|
||||
"name": "pressure",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"altitude": {
|
||||
"name": "altitude",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time": {
|
||||
"name": "time",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"sensor_data_sensor_sensors_id_fk": {
|
||||
"name": "sensor_data_sensor_sensors_id_fk",
|
||||
"tableFrom": "sensor_data",
|
||||
"tableTo": "sensors",
|
||||
"columnsFrom": ["sensor"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.sensors": {
|
||||
"name": "sensors",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"sensors_user_users_id_fk": {
|
||||
"name": "sensors_user_users_id_fk",
|
||||
"tableFrom": "sensors",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["user"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"sessions_user_id_users_id_fk": {
|
||||
"name": "sessions_user_id_users_id_fk",
|
||||
"tableFrom": "sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"age": {
|
||||
"name": "age",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"password_hash": {
|
||||
"name": "password_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["username"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@
|
|||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1730747291671,
|
||||
"tag": "20241104190811_lucia",
|
||||
"when": 1749889934943,
|
||||
"tag": "20250614083214_lucia",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
// src/lib/server/mqtt-devices.js
|
||||
// This module provides access to MQTT device data for other parts of the application
|
||||
|
||||
// We'll import the maps directly from the MQTT server module
|
||||
|
||||
let connectedDevices = new Map();
|
||||
let deviceSensorData = new Map();
|
||||
|
||||
// Export the maps so the MQTT server can set them
|
||||
export { connectedDevices, deviceSensorData };
|
||||
|
||||
// Clean up offline devices (not seen in last 5 minutes)
|
||||
function cleanupDevices() {
|
||||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||||
let updated = false;
|
||||
|
@ -24,7 +17,6 @@ function cleanupDevices() {
|
|||
return updated;
|
||||
}
|
||||
|
||||
// Export function to get current devices
|
||||
export function getCurrentDevices() {
|
||||
cleanupDevices();
|
||||
return Array.from(connectedDevices.values()).map((device) => {
|
||||
|
@ -36,7 +28,6 @@ export function getCurrentDevices() {
|
|||
});
|
||||
}
|
||||
|
||||
// Export function to get sensor data for a specific device
|
||||
export function getDeviceSensorData(deviceId) {
|
||||
return deviceSensorData.get(deviceId) || null;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,11 @@ import { db } from "$lib/server/db";
|
|||
import * as table from "$lib/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import { connect } from "mqtt";
|
||||
import { getDeviceSensorData } from "$lib/server/mqtt-devices.js";
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
// Convert slug to number for floor lookup
|
||||
const floorNumber = Number(params.slug);
|
||||
|
||||
// First check if we have a saved floor configuration in the floors table
|
||||
const floorData = await db.select({
|
||||
floor: table.floors.floor,
|
||||
url: table.floors.url
|
||||
|
@ -17,10 +14,8 @@ export const load: PageServerLoad = async ({ params }) => {
|
|||
|
||||
if (floorData.length > 0 && floorData[0].url && floorData[0].url !== "/") {
|
||||
try {
|
||||
// Try to parse the saved configuration
|
||||
const config = JSON.parse(floorData[0].url);
|
||||
|
||||
// Add real sensor data to devices
|
||||
if (config.devices) {
|
||||
config.devices = config.devices.map(device => {
|
||||
const sensorData = getDeviceSensorData(device.id);
|
||||
|
@ -41,7 +36,6 @@ export const load: PageServerLoad = async ({ params }) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Fallback to the old canvas drawing system
|
||||
const floor_cnt = await db.select({ floor: table.plans.floor, json: table.plans.plan }).from(table.plans).where(eq(table.plans.floor, params.slug));
|
||||
if (floor_cnt.length == 0) {
|
||||
await db.insert(table.plans).values({ floor: params.slug, plan: {
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
let refreshInterval;
|
||||
|
||||
onMount(() => {
|
||||
// Connect to the SSE endpoint
|
||||
console.log("Attempting to connect to MQTT SSE...");
|
||||
eventSource = new EventSource("/mqtt");
|
||||
|
||||
|
@ -32,7 +31,6 @@
|
|||
const messageData = JSON.parse(event.data);
|
||||
console.log("Floor page received SSE data:", messageData);
|
||||
|
||||
// Handle device updates
|
||||
if (messageData.devices) {
|
||||
console.log("Received devices:", messageData.devices);
|
||||
const newData = new Map();
|
||||
|
@ -46,7 +44,6 @@
|
|||
console.log("Updated deviceSensorData:", deviceSensorData);
|
||||
}
|
||||
|
||||
// Handle MQTT message if present
|
||||
if (messageData.message) {
|
||||
mqttMessage = messageData.message;
|
||||
} else {
|
||||
|
@ -59,7 +56,7 @@
|
|||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error("SSE Error:", error);
|
||||
eventSource.close(); // Close the connection on error
|
||||
eventSource.close();
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -82,7 +79,6 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Draw walls, doors, and furniture (simplified example)
|
||||
drawRegions(data.regions);
|
||||
drawDoors(data.doors);
|
||||
drawFurniture(data.furnitures);
|
||||
|
@ -100,7 +96,7 @@
|
|||
function drawDoors(doors) {
|
||||
doors.forEach((door) => {
|
||||
ctx.beginPath();
|
||||
ctx.rect(door.location.x, door.location.y, door.width, 5); // Simplified door drawing
|
||||
ctx.rect(door.location.x, door.location.y, door.width, 5);
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
|
@ -455,7 +451,6 @@
|
|||
<Card.Content>
|
||||
<div class="space-y-4">
|
||||
{#if filteredData.length > 0}
|
||||
<!-- Statistics Summary -->
|
||||
<div class="grid grid-cols-3 gap-4 rounded border p-4 text-center">
|
||||
<div>
|
||||
<div class="text-muted-foreground text-sm font-medium">Latest</div>
|
||||
|
@ -497,10 +492,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Simple SVG Line Chart -->
|
||||
<div class="rounded border bg-white p-4">
|
||||
<svg viewBox="0 0 800 400" class="h-80 w-full">
|
||||
<!-- Chart area -->
|
||||
{#if filteredData.length > 1}
|
||||
{@const minValue = Math.min(...filteredData.map((d) => d[selectedSensor]))}
|
||||
{@const maxValue = Math.max(...filteredData.map((d) => d[selectedSensor]))}
|
||||
|
@ -509,7 +502,6 @@
|
|||
{@const chartWidth = 800 - padding * 2}
|
||||
{@const chartHeight = 400 - padding * 2}
|
||||
|
||||
<!-- Background grid -->
|
||||
<defs>
|
||||
<pattern id="grid" width="50" height="40" patternUnits="userSpaceOnUse">
|
||||
<path
|
||||
|
@ -528,7 +520,6 @@
|
|||
fill="url(#grid)"
|
||||
/>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="25" y={padding + 5} class="fill-gray-600 text-xs" text-anchor="end"
|
||||
>{maxValue.toFixed(1)}</text
|
||||
>
|
||||
|
@ -545,14 +536,12 @@
|
|||
text-anchor="end">{minValue.toFixed(1)}</text
|
||||
>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
{#if filteredData.length > 0}
|
||||
{@const firstDate = filteredData[0].date}
|
||||
{@const lastDate = filteredData[filteredData.length - 1].date}
|
||||
{@const midIndex = Math.floor(filteredData.length / 2)}
|
||||
{@const midDate = filteredData[midIndex].date}
|
||||
|
||||
<!-- Format dates based on time range -->
|
||||
{@const formatDate = (date) => {
|
||||
if (timeRange === "7d") {
|
||||
return date.toLocaleDateString("en-US", {
|
||||
|
@ -590,7 +579,6 @@
|
|||
>
|
||||
{/if}
|
||||
|
||||
<!-- Data line -->
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#3b82f6"
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as table from "$lib/server/db/schema"
|
|||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const GET = async (event) => {
|
||||
// Get all sensor data (like the original working version)
|
||||
console.log(event.locals.session.userId)
|
||||
const rawData = await db.select({
|
||||
altitude: table.sensorData.altitude,
|
||||
|
@ -15,11 +14,9 @@ export const GET = async (event) => {
|
|||
user: table.sensors.user,
|
||||
}).from(table.sensorData).innerJoin(table.sensors, eq(table.sensors.id, table.sensorData.sensor)).where(eq(table.sensors.user, event.locals.session.userId));
|
||||
|
||||
// Scale pressure values to be in a similar range as other sensors
|
||||
// Divide by 1000 to convert from Pa to kPa (more reasonable scale)
|
||||
const data = rawData.map(item => ({
|
||||
...item,
|
||||
pressure: Math.round((item.pressure / 1000) * 10) / 10 // Convert to kPa with 1 decimal place
|
||||
pressure: Math.round((item.pressure / 1000) * 10) / 10
|
||||
}));
|
||||
|
||||
console.log(`Returning ${data.length} data points`);
|
||||
|
|
|
@ -5,16 +5,13 @@ import { fail } from "@sveltejs/kit";
|
|||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const load = async (event) => {
|
||||
// Fetch all available floors
|
||||
const floors = await db
|
||||
.select({ floor: table.floors.floor })
|
||||
.from(table.floors)
|
||||
.orderBy(table.floors.floor);
|
||||
|
||||
// Get real connected devices from MQTT
|
||||
let devices = getCurrentDevices();
|
||||
|
||||
// If no real devices, provide fallback mock devices
|
||||
if (devices.length === 0) {
|
||||
devices = [
|
||||
{
|
||||
|
@ -85,7 +82,6 @@ export const actions = {
|
|||
await db.insert(table.sensors).values({ id: dev.id, user: event.locals.session.userId });
|
||||
});
|
||||
|
||||
// Check if floor exists
|
||||
const exists = await db
|
||||
.select({ floor: table.floors.floor })
|
||||
.from(table.floors)
|
||||
|
@ -95,9 +91,6 @@ export const actions = {
|
|||
return fail(400, { message: `Floor ${n} does not exist! Please create it first.` });
|
||||
}
|
||||
|
||||
// Update floor with configuration
|
||||
// Note: In a real implementation, you would store this data properly
|
||||
// For now, we'll just update the url field as a JSON string
|
||||
const floorConfig = {
|
||||
image: floorPlanImage,
|
||||
devices: deviceData,
|
||||
|
|
|
@ -31,13 +31,11 @@
|
|||
});
|
||||
|
||||
$effect(() => {
|
||||
// Update devices when data changes
|
||||
if (data.devices) {
|
||||
availableDevices = data.devices;
|
||||
}
|
||||
});
|
||||
|
||||
// Set up real-time updates via SSE
|
||||
onMount(() => {
|
||||
console.log("Settings: Attempting to connect to MQTT SSE...");
|
||||
eventSource = new EventSource("/mqtt");
|
||||
|
@ -56,13 +54,10 @@
|
|||
const messageData = JSON.parse(event.data);
|
||||
console.log("Settings page received SSE data:", messageData);
|
||||
|
||||
// Update device sensor data and device list from real-time updates
|
||||
if (messageData.devices) {
|
||||
console.log("Updating available devices with:", messageData.devices);
|
||||
// Update available devices with latest data
|
||||
availableDevices = messageData.devices;
|
||||
|
||||
// Update sensor data map
|
||||
const newData = new Map();
|
||||
messageData.devices.forEach((device) => {
|
||||
if (device.sensorData) {
|
||||
|
@ -116,14 +111,11 @@
|
|||
const x = ((event.clientX - rect.left) / rect.width) * 100;
|
||||
const y = ((event.clientY - rect.top) / rect.height) * 100;
|
||||
|
||||
// Check if device is already placed
|
||||
const existingIndex = placedDevices.findIndex((d) => d.id === draggedDevice.id);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// Update position
|
||||
placedDevices[existingIndex] = { ...placedDevices[existingIndex], x, y };
|
||||
} else {
|
||||
// Add new device
|
||||
placedDevices = [...placedDevices, { ...draggedDevice, x, y }];
|
||||
}
|
||||
|
||||
|
@ -244,7 +236,6 @@
|
|||
return async ({ result }) => {
|
||||
if (result.type === "success") {
|
||||
saveMessage = { type: "success", text: result.data.message };
|
||||
// Clear form after successful save
|
||||
selectedFloorNumber = "";
|
||||
} else if (result.type === "failure") {
|
||||
saveMessage = {
|
||||
|
|
|
@ -63,7 +63,6 @@ export const actions = {
|
|||
|
||||
const userId = generateUserId();
|
||||
const passwordHash = await hash(password, {
|
||||
// recommended minimum parameters
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
outputLen: 32,
|
||||
|
@ -84,7 +83,6 @@ export const actions = {
|
|||
};
|
||||
|
||||
function generateUserId() {
|
||||
// ID with 120 bits of entropy, or about the same as UUID v4.
|
||||
const bytes = crypto.getRandomValues(new Uint8Array(15));
|
||||
const id = encodeBase64url(bytes);
|
||||
return id;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// src/routes/mqtt/+server.js
|
||||
import { db } from "$lib/server/db";
|
||||
import * as table from "$lib/server/db/schema";
|
||||
import { connectedDevices, deviceSensorData, getCurrentDevices } from "$lib/server/mqtt-devices.js";
|
||||
|
@ -6,20 +5,15 @@ import { eq } from "drizzle-orm";
|
|||
import mqtt from "mqtt";
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
// A Svelte store to hold the latest MQTT message.
|
||||
// In a real application, you might want to store more data or
|
||||
// use a more robust way to manage messages, but for a basic example, this works.
|
||||
const latestMessage = writable("No message yet");
|
||||
const devices = writable([]);
|
||||
|
||||
let client = null;
|
||||
|
||||
// Function to update device status and sensor data
|
||||
function updateDevice(deviceId, sensorData = null) {
|
||||
const now = new Date();
|
||||
const deviceName = `ESP8266 #${deviceId.slice(-6)}`;
|
||||
|
||||
// Update device info
|
||||
connectedDevices.set(deviceId, {
|
||||
id: deviceId,
|
||||
name: deviceName,
|
||||
|
@ -28,7 +22,6 @@ function updateDevice(deviceId, sensorData = null) {
|
|||
lastSeen: now,
|
||||
});
|
||||
|
||||
// Update sensor data if provided
|
||||
if (sensorData) {
|
||||
deviceSensorData.set(deviceId, {
|
||||
...sensorData,
|
||||
|
@ -36,11 +29,9 @@ function updateDevice(deviceId, sensorData = null) {
|
|||
});
|
||||
}
|
||||
|
||||
// Update the devices store
|
||||
updateDevicesStore();
|
||||
}
|
||||
|
||||
// Function to parse sensor data from MQTT payload
|
||||
function parseSensorData(payload) {
|
||||
try {
|
||||
const data = JSON.parse(payload);
|
||||
|
@ -56,10 +47,8 @@ function parseSensorData(payload) {
|
|||
}
|
||||
}
|
||||
|
||||
// Store SSE controllers for cleanup
|
||||
const sseControllers = new Set();
|
||||
|
||||
// Function to update devices store with latest data
|
||||
function updateDevicesStore() {
|
||||
const deviceList = Array.from(connectedDevices.values()).map((device) => {
|
||||
const sensorData = deviceSensorData.get(device.id);
|
||||
|
@ -71,7 +60,6 @@ function updateDevicesStore() {
|
|||
devices.set(deviceList);
|
||||
}
|
||||
|
||||
// Clean up offline devices (not seen in last 5 minutes)
|
||||
function cleanupDevices() {
|
||||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||||
let updated = false;
|
||||
|
@ -88,21 +76,15 @@ function cleanupDevices() {
|
|||
}
|
||||
}
|
||||
|
||||
// Run cleanup every minute
|
||||
setInterval(cleanupDevices, 60000);
|
||||
|
||||
// getCurrentDevices is now imported from the shared module
|
||||
|
||||
// Function to connect to MQTT
|
||||
function connectMqtt() {
|
||||
if (client && client.connected) {
|
||||
return; // Already connected
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace with your MQTT broker details
|
||||
const BROKER_URL = "mqtt://kada49.it:1883"; // Example public broker
|
||||
//const BROKER_URL = "mqtt://test.mosquitto.org:1883";
|
||||
const TOPIC = "esp8266/+/data"; // Replace with your desired topic
|
||||
const BROKER_URL = "mqtt://kada49.it:1883";
|
||||
const TOPIC = "esp8266/+/data";
|
||||
|
||||
client = mqtt.connect(BROKER_URL);
|
||||
|
||||
|
@ -120,32 +102,27 @@ function connectMqtt() {
|
|||
client.on("message", async (topic, message) => {
|
||||
const payload = message.toString();
|
||||
console.log(`Received message from topic "${topic}": ${payload}`);
|
||||
latestMessage.set(payload); // Update the Svelte store
|
||||
latestMessage.set(payload);
|
||||
|
||||
// Extract device ID from topic (esp8266/DEVICE_ID/data)
|
||||
const topicParts = topic.split("/");
|
||||
if (topicParts.length >= 3 && topicParts[0] === "esp8266" && topicParts[2] === "data") {
|
||||
const deviceId = topicParts[1];
|
||||
console.log(`Processing data for device: ${deviceId}`);
|
||||
|
||||
// Parse sensor data
|
||||
const sensorData = parseSensorData(payload);
|
||||
if (sensorData) {
|
||||
console.log(`Parsed sensor data:`, sensorData);
|
||||
updateDevice(deviceId, sensorData);
|
||||
const devices = await db.select().from(table.sensors).where(eq(table.sensors.id, deviceId));
|
||||
if (devices.length == 1)
|
||||
await db
|
||||
.insert(table.sensorData)
|
||||
.values({
|
||||
sensor: deviceId,
|
||||
temperature: sensorData.temperature,
|
||||
humidity: sensorData.humidity,
|
||||
altitude: sensorData.altitude,
|
||||
pressure: sensorData.pressure,
|
||||
});
|
||||
await db.insert(table.sensorData).values({
|
||||
sensor: deviceId,
|
||||
temperature: sensorData.temperature,
|
||||
humidity: sensorData.humidity,
|
||||
altitude: sensorData.altitude,
|
||||
pressure: sensorData.pressure,
|
||||
});
|
||||
} else {
|
||||
// Still update device as online even if data parsing failed
|
||||
updateDevice(deviceId);
|
||||
}
|
||||
}
|
||||
|
@ -154,20 +131,17 @@ function connectMqtt() {
|
|||
client.on("error", (err) => {
|
||||
console.error(`MQTT error: ${err}`);
|
||||
if (client) {
|
||||
client.end(); // Close connection on error
|
||||
client.end();
|
||||
}
|
||||
client = null;
|
||||
// Implement re-connection logic here if needed
|
||||
});
|
||||
|
||||
client.on("close", () => {
|
||||
console.log("MQTT connection closed.");
|
||||
client = null;
|
||||
// Implement re-connection logic here if needed
|
||||
});
|
||||
}
|
||||
|
||||
// Connect to MQTT when the server starts
|
||||
connectMqtt();
|
||||
|
||||
/** @type {import("./$types").RequestHandler} */
|
||||
|
@ -186,10 +160,8 @@ export async function GET({ request }) {
|
|||
let isConnected = true;
|
||||
let interval;
|
||||
|
||||
// Add this controller to the broadcast set
|
||||
sseControllers.add(controller);
|
||||
|
||||
// Cleanup function
|
||||
const cleanup = () => {
|
||||
console.log("Cleaning up MQTT SSE connection");
|
||||
isConnected = false;
|
||||
|
@ -203,7 +175,6 @@ export async function GET({ request }) {
|
|||
}
|
||||
};
|
||||
|
||||
// Send current device data
|
||||
function sendDevices() {
|
||||
if (!isConnected) {
|
||||
cleanup();
|
||||
|
@ -217,12 +188,10 @@ export async function GET({ request }) {
|
|||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Check if controller is still valid before enqueueing
|
||||
if (isConnected) {
|
||||
controller.enqueue(`data: ${JSON.stringify(message)}\n\n`);
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle controller closed error specifically
|
||||
if (
|
||||
error.code === "ERR_INVALID_STATE" ||
|
||||
error.message.includes("Controller is already closed")
|
||||
|
@ -235,13 +204,10 @@ export async function GET({ request }) {
|
|||
}
|
||||
}
|
||||
|
||||
// Send initial data
|
||||
sendDevices();
|
||||
|
||||
// Send updates every 2 seconds
|
||||
interval = setInterval(sendDevices, 2000);
|
||||
|
||||
// Subscribe to MQTT messages via the store for real-time updates
|
||||
const unsubscribe = latestMessage.subscribe((message) => {
|
||||
if (message !== "No message yet" && isConnected) {
|
||||
try {
|
||||
|
@ -265,7 +231,6 @@ export async function GET({ request }) {
|
|||
}
|
||||
});
|
||||
|
||||
// Handle client disconnection
|
||||
request.signal.addEventListener("abort", () => {
|
||||
console.log("Client disconnected from MQTT SSE stream");
|
||||
cleanup();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue