Compare commits

...

2 commits

Author SHA1 Message Date
0f79e7d121
updated db 2025-06-15 14:23:37 +02:00
b8b0f2d6a1
remove unused imports 2025-06-15 14:23:16 +02:00
13 changed files with 321 additions and 222 deletions

View file

@ -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 $$;

View 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;

View file

@ -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": {}
}
}

View 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": {}
}
}

View file

@ -5,8 +5,8 @@
{ {
"idx": 0, "idx": 0,
"version": "7", "version": "7",
"when": 1730747291671, "when": 1749889934943,
"tag": "20241104190811_lucia", "tag": "20250614083214_lucia",
"breakpoints": true "breakpoints": true
} }
] ]

View file

@ -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 connectedDevices = new Map();
let deviceSensorData = new Map(); let deviceSensorData = new Map();
// Export the maps so the MQTT server can set them
export { connectedDevices, deviceSensorData }; export { connectedDevices, deviceSensorData };
// Clean up offline devices (not seen in last 5 minutes)
function cleanupDevices() { function cleanupDevices() {
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
let updated = false; let updated = false;
@ -24,7 +17,6 @@ function cleanupDevices() {
return updated; return updated;
} }
// Export function to get current devices
export function getCurrentDevices() { export function getCurrentDevices() {
cleanupDevices(); cleanupDevices();
return Array.from(connectedDevices.values()).map((device) => { 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) { export function getDeviceSensorData(deviceId) {
return deviceSensorData.get(deviceId) || null; return deviceSensorData.get(deviceId) || null;
} }

View file

@ -2,14 +2,11 @@ import { db } from "$lib/server/db";
import * as table from "$lib/server/db/schema"; import * as table from "$lib/server/db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import type { PageServerLoad } from "./$types"; import type { PageServerLoad } from "./$types";
import { connect } from "mqtt";
import { getDeviceSensorData } from "$lib/server/mqtt-devices.js"; import { getDeviceSensorData } from "$lib/server/mqtt-devices.js";
export const load: PageServerLoad = async ({ params }) => { export const load: PageServerLoad = async ({ params }) => {
// Convert slug to number for floor lookup
const floorNumber = Number(params.slug); const floorNumber = Number(params.slug);
// First check if we have a saved floor configuration in the floors table
const floorData = await db.select({ const floorData = await db.select({
floor: table.floors.floor, floor: table.floors.floor,
url: table.floors.url url: table.floors.url
@ -17,10 +14,8 @@ export const load: PageServerLoad = async ({ params }) => {
if (floorData.length > 0 && floorData[0].url && floorData[0].url !== "/") { if (floorData.length > 0 && floorData[0].url && floorData[0].url !== "/") {
try { try {
// Try to parse the saved configuration
const config = JSON.parse(floorData[0].url); const config = JSON.parse(floorData[0].url);
// Add real sensor data to devices
if (config.devices) { if (config.devices) {
config.devices = config.devices.map(device => { config.devices = config.devices.map(device => {
const sensorData = getDeviceSensorData(device.id); 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)); 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) { if (floor_cnt.length == 0) {
await db.insert(table.plans).values({ floor: params.slug, plan: { await db.insert(table.plans).values({ floor: params.slug, plan: {

View file

@ -14,7 +14,6 @@
let refreshInterval; let refreshInterval;
onMount(() => { onMount(() => {
// Connect to the SSE endpoint
console.log("Attempting to connect to MQTT SSE..."); console.log("Attempting to connect to MQTT SSE...");
eventSource = new EventSource("/mqtt"); eventSource = new EventSource("/mqtt");
@ -32,7 +31,6 @@
const messageData = JSON.parse(event.data); const messageData = JSON.parse(event.data);
console.log("Floor page received SSE data:", messageData); console.log("Floor page received SSE data:", messageData);
// Handle device updates
if (messageData.devices) { if (messageData.devices) {
console.log("Received devices:", messageData.devices); console.log("Received devices:", messageData.devices);
const newData = new Map(); const newData = new Map();
@ -46,7 +44,6 @@
console.log("Updated deviceSensorData:", deviceSensorData); console.log("Updated deviceSensorData:", deviceSensorData);
} }
// Handle MQTT message if present
if (messageData.message) { if (messageData.message) {
mqttMessage = messageData.message; mqttMessage = messageData.message;
} else { } else {
@ -59,7 +56,7 @@
eventSource.onerror = (error) => { eventSource.onerror = (error) => {
console.error("SSE Error:", error); console.error("SSE Error:", error);
eventSource.close(); // Close the connection on error eventSource.close();
}; };
}); });
@ -82,7 +79,6 @@
return; return;
} }
// Draw walls, doors, and furniture (simplified example)
drawRegions(data.regions); drawRegions(data.regions);
drawDoors(data.doors); drawDoors(data.doors);
drawFurniture(data.furnitures); drawFurniture(data.furnitures);
@ -100,7 +96,7 @@
function drawDoors(doors) { function drawDoors(doors) {
doors.forEach((door) => { doors.forEach((door) => {
ctx.beginPath(); 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(); ctx.stroke();
}); });
} }
@ -455,7 +451,6 @@
<Card.Content> <Card.Content>
<div class="space-y-4"> <div class="space-y-4">
{#if filteredData.length > 0} {#if filteredData.length > 0}
<!-- Statistics Summary -->
<div class="grid grid-cols-3 gap-4 rounded border p-4 text-center"> <div class="grid grid-cols-3 gap-4 rounded border p-4 text-center">
<div> <div>
<div class="text-muted-foreground text-sm font-medium">Latest</div> <div class="text-muted-foreground text-sm font-medium">Latest</div>
@ -497,10 +492,8 @@
</div> </div>
</div> </div>
<!-- Simple SVG Line Chart -->
<div class="rounded border bg-white p-4"> <div class="rounded border bg-white p-4">
<svg viewBox="0 0 800 400" class="h-80 w-full"> <svg viewBox="0 0 800 400" class="h-80 w-full">
<!-- Chart area -->
{#if filteredData.length > 1} {#if filteredData.length > 1}
{@const minValue = Math.min(...filteredData.map((d) => d[selectedSensor]))} {@const minValue = Math.min(...filteredData.map((d) => d[selectedSensor]))}
{@const maxValue = Math.max(...filteredData.map((d) => d[selectedSensor]))} {@const maxValue = Math.max(...filteredData.map((d) => d[selectedSensor]))}
@ -509,7 +502,6 @@
{@const chartWidth = 800 - padding * 2} {@const chartWidth = 800 - padding * 2}
{@const chartHeight = 400 - padding * 2} {@const chartHeight = 400 - padding * 2}
<!-- Background grid -->
<defs> <defs>
<pattern id="grid" width="50" height="40" patternUnits="userSpaceOnUse"> <pattern id="grid" width="50" height="40" patternUnits="userSpaceOnUse">
<path <path
@ -528,7 +520,6 @@
fill="url(#grid)" fill="url(#grid)"
/> />
<!-- Y-axis labels -->
<text x="25" y={padding + 5} class="fill-gray-600 text-xs" text-anchor="end" <text x="25" y={padding + 5} class="fill-gray-600 text-xs" text-anchor="end"
>{maxValue.toFixed(1)}</text >{maxValue.toFixed(1)}</text
> >
@ -545,14 +536,12 @@
text-anchor="end">{minValue.toFixed(1)}</text text-anchor="end">{minValue.toFixed(1)}</text
> >
<!-- X-axis labels -->
{#if filteredData.length > 0} {#if filteredData.length > 0}
{@const firstDate = filteredData[0].date} {@const firstDate = filteredData[0].date}
{@const lastDate = filteredData[filteredData.length - 1].date} {@const lastDate = filteredData[filteredData.length - 1].date}
{@const midIndex = Math.floor(filteredData.length / 2)} {@const midIndex = Math.floor(filteredData.length / 2)}
{@const midDate = filteredData[midIndex].date} {@const midDate = filteredData[midIndex].date}
<!-- Format dates based on time range -->
{@const formatDate = (date) => { {@const formatDate = (date) => {
if (timeRange === "7d") { if (timeRange === "7d") {
return date.toLocaleDateString("en-US", { return date.toLocaleDateString("en-US", {
@ -590,7 +579,6 @@
> >
{/if} {/if}
<!-- Data line -->
<polyline <polyline
fill="none" fill="none"
stroke="#3b82f6" stroke="#3b82f6"

View file

@ -3,7 +3,6 @@ import * as table from "$lib/server/db/schema"
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
export const GET = async (event) => { export const GET = async (event) => {
// Get all sensor data (like the original working version)
console.log(event.locals.session.userId) console.log(event.locals.session.userId)
const rawData = await db.select({ const rawData = await db.select({
altitude: table.sensorData.altitude, altitude: table.sensorData.altitude,
@ -15,11 +14,9 @@ export const GET = async (event) => {
user: table.sensors.user, 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)); }).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 => ({ const data = rawData.map(item => ({
...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`); console.log(`Returning ${data.length} data points`);

View file

@ -5,16 +5,13 @@ import { fail } from "@sveltejs/kit";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
export const load = async (event) => { export const load = async (event) => {
// Fetch all available floors
const floors = await db const floors = await db
.select({ floor: table.floors.floor }) .select({ floor: table.floors.floor })
.from(table.floors) .from(table.floors)
.orderBy(table.floors.floor); .orderBy(table.floors.floor);
// Get real connected devices from MQTT
let devices = getCurrentDevices(); let devices = getCurrentDevices();
// If no real devices, provide fallback mock devices
if (devices.length === 0) { if (devices.length === 0) {
devices = [ devices = [
{ {
@ -85,7 +82,6 @@ export const actions = {
await db.insert(table.sensors).values({ id: dev.id, user: event.locals.session.userId }); await db.insert(table.sensors).values({ id: dev.id, user: event.locals.session.userId });
}); });
// Check if floor exists
const exists = await db const exists = await db
.select({ floor: table.floors.floor }) .select({ floor: table.floors.floor })
.from(table.floors) .from(table.floors)
@ -95,9 +91,6 @@ export const actions = {
return fail(400, { message: `Floor ${n} does not exist! Please create it first.` }); 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 = { const floorConfig = {
image: floorPlanImage, image: floorPlanImage,
devices: deviceData, devices: deviceData,

View file

@ -31,13 +31,11 @@
}); });
$effect(() => { $effect(() => {
// Update devices when data changes
if (data.devices) { if (data.devices) {
availableDevices = data.devices; availableDevices = data.devices;
} }
}); });
// Set up real-time updates via SSE
onMount(() => { onMount(() => {
console.log("Settings: Attempting to connect to MQTT SSE..."); console.log("Settings: Attempting to connect to MQTT SSE...");
eventSource = new EventSource("/mqtt"); eventSource = new EventSource("/mqtt");
@ -56,13 +54,10 @@
const messageData = JSON.parse(event.data); const messageData = JSON.parse(event.data);
console.log("Settings page received SSE data:", messageData); console.log("Settings page received SSE data:", messageData);
// Update device sensor data and device list from real-time updates
if (messageData.devices) { if (messageData.devices) {
console.log("Updating available devices with:", messageData.devices); console.log("Updating available devices with:", messageData.devices);
// Update available devices with latest data
availableDevices = messageData.devices; availableDevices = messageData.devices;
// Update sensor data map
const newData = new Map(); const newData = new Map();
messageData.devices.forEach((device) => { messageData.devices.forEach((device) => {
if (device.sensorData) { if (device.sensorData) {
@ -116,14 +111,11 @@
const x = ((event.clientX - rect.left) / rect.width) * 100; const x = ((event.clientX - rect.left) / rect.width) * 100;
const y = ((event.clientY - rect.top) / rect.height) * 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); const existingIndex = placedDevices.findIndex((d) => d.id === draggedDevice.id);
if (existingIndex >= 0) { if (existingIndex >= 0) {
// Update position
placedDevices[existingIndex] = { ...placedDevices[existingIndex], x, y }; placedDevices[existingIndex] = { ...placedDevices[existingIndex], x, y };
} else { } else {
// Add new device
placedDevices = [...placedDevices, { ...draggedDevice, x, y }]; placedDevices = [...placedDevices, { ...draggedDevice, x, y }];
} }
@ -244,7 +236,6 @@
return async ({ result }) => { return async ({ result }) => {
if (result.type === "success") { if (result.type === "success") {
saveMessage = { type: "success", text: result.data.message }; saveMessage = { type: "success", text: result.data.message };
// Clear form after successful save
selectedFloorNumber = ""; selectedFloorNumber = "";
} else if (result.type === "failure") { } else if (result.type === "failure") {
saveMessage = { saveMessage = {

View file

@ -63,7 +63,6 @@ export const actions = {
const userId = generateUserId(); const userId = generateUserId();
const passwordHash = await hash(password, { const passwordHash = await hash(password, {
// recommended minimum parameters
memoryCost: 19456, memoryCost: 19456,
timeCost: 2, timeCost: 2,
outputLen: 32, outputLen: 32,
@ -84,7 +83,6 @@ export const actions = {
}; };
function generateUserId() { function generateUserId() {
// ID with 120 bits of entropy, or about the same as UUID v4.
const bytes = crypto.getRandomValues(new Uint8Array(15)); const bytes = crypto.getRandomValues(new Uint8Array(15));
const id = encodeBase64url(bytes); const id = encodeBase64url(bytes);
return id; return id;

View file

@ -1,4 +1,3 @@
// src/routes/mqtt/+server.js
import { db } from "$lib/server/db"; import { db } from "$lib/server/db";
import * as table from "$lib/server/db/schema"; import * as table from "$lib/server/db/schema";
import { connectedDevices, deviceSensorData, getCurrentDevices } from "$lib/server/mqtt-devices.js"; import { connectedDevices, deviceSensorData, getCurrentDevices } from "$lib/server/mqtt-devices.js";
@ -6,20 +5,15 @@ import { eq } from "drizzle-orm";
import mqtt from "mqtt"; import mqtt from "mqtt";
import { writable } from "svelte/store"; 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 latestMessage = writable("No message yet");
const devices = writable([]); const devices = writable([]);
let client = null; let client = null;
// Function to update device status and sensor data
function updateDevice(deviceId, sensorData = null) { function updateDevice(deviceId, sensorData = null) {
const now = new Date(); const now = new Date();
const deviceName = `ESP8266 #${deviceId.slice(-6)}`; const deviceName = `ESP8266 #${deviceId.slice(-6)}`;
// Update device info
connectedDevices.set(deviceId, { connectedDevices.set(deviceId, {
id: deviceId, id: deviceId,
name: deviceName, name: deviceName,
@ -28,7 +22,6 @@ function updateDevice(deviceId, sensorData = null) {
lastSeen: now, lastSeen: now,
}); });
// Update sensor data if provided
if (sensorData) { if (sensorData) {
deviceSensorData.set(deviceId, { deviceSensorData.set(deviceId, {
...sensorData, ...sensorData,
@ -36,11 +29,9 @@ function updateDevice(deviceId, sensorData = null) {
}); });
} }
// Update the devices store
updateDevicesStore(); updateDevicesStore();
} }
// Function to parse sensor data from MQTT payload
function parseSensorData(payload) { function parseSensorData(payload) {
try { try {
const data = JSON.parse(payload); const data = JSON.parse(payload);
@ -56,10 +47,8 @@ function parseSensorData(payload) {
} }
} }
// Store SSE controllers for cleanup
const sseControllers = new Set(); const sseControllers = new Set();
// Function to update devices store with latest data
function updateDevicesStore() { function updateDevicesStore() {
const deviceList = Array.from(connectedDevices.values()).map((device) => { const deviceList = Array.from(connectedDevices.values()).map((device) => {
const sensorData = deviceSensorData.get(device.id); const sensorData = deviceSensorData.get(device.id);
@ -71,7 +60,6 @@ function updateDevicesStore() {
devices.set(deviceList); devices.set(deviceList);
} }
// Clean up offline devices (not seen in last 5 minutes)
function cleanupDevices() { function cleanupDevices() {
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
let updated = false; let updated = false;
@ -88,21 +76,15 @@ function cleanupDevices() {
} }
} }
// Run cleanup every minute
setInterval(cleanupDevices, 60000); setInterval(cleanupDevices, 60000);
// getCurrentDevices is now imported from the shared module
// Function to connect to MQTT
function connectMqtt() { function connectMqtt() {
if (client && client.connected) { if (client && client.connected) {
return; // Already connected return;
} }
// Replace with your MQTT broker details const BROKER_URL = "mqtt://kada49.it:1883";
const BROKER_URL = "mqtt://kada49.it:1883"; // Example public broker const TOPIC = "esp8266/+/data";
//const BROKER_URL = "mqtt://test.mosquitto.org:1883";
const TOPIC = "esp8266/+/data"; // Replace with your desired topic
client = mqtt.connect(BROKER_URL); client = mqtt.connect(BROKER_URL);
@ -120,32 +102,27 @@ function connectMqtt() {
client.on("message", async (topic, message) => { client.on("message", async (topic, message) => {
const payload = message.toString(); const payload = message.toString();
console.log(`Received message from topic "${topic}": ${payload}`); 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("/"); const topicParts = topic.split("/");
if (topicParts.length >= 3 && topicParts[0] === "esp8266" && topicParts[2] === "data") { if (topicParts.length >= 3 && topicParts[0] === "esp8266" && topicParts[2] === "data") {
const deviceId = topicParts[1]; const deviceId = topicParts[1];
console.log(`Processing data for device: ${deviceId}`); console.log(`Processing data for device: ${deviceId}`);
// Parse sensor data
const sensorData = parseSensorData(payload); const sensorData = parseSensorData(payload);
if (sensorData) { if (sensorData) {
console.log(`Parsed sensor data:`, sensorData); console.log(`Parsed sensor data:`, sensorData);
updateDevice(deviceId, sensorData); updateDevice(deviceId, sensorData);
const devices = await db.select().from(table.sensors).where(eq(table.sensors.id, deviceId)); const devices = await db.select().from(table.sensors).where(eq(table.sensors.id, deviceId));
if (devices.length == 1) if (devices.length == 1)
await db await db.insert(table.sensorData).values({
.insert(table.sensorData) sensor: deviceId,
.values({ temperature: sensorData.temperature,
sensor: deviceId, humidity: sensorData.humidity,
temperature: sensorData.temperature, altitude: sensorData.altitude,
humidity: sensorData.humidity, pressure: sensorData.pressure,
altitude: sensorData.altitude, });
pressure: sensorData.pressure,
});
} else { } else {
// Still update device as online even if data parsing failed
updateDevice(deviceId); updateDevice(deviceId);
} }
} }
@ -154,20 +131,17 @@ function connectMqtt() {
client.on("error", (err) => { client.on("error", (err) => {
console.error(`MQTT error: ${err}`); console.error(`MQTT error: ${err}`);
if (client) { if (client) {
client.end(); // Close connection on error client.end();
} }
client = null; client = null;
// Implement re-connection logic here if needed
}); });
client.on("close", () => { client.on("close", () => {
console.log("MQTT connection closed."); console.log("MQTT connection closed.");
client = null; client = null;
// Implement re-connection logic here if needed
}); });
} }
// Connect to MQTT when the server starts
connectMqtt(); connectMqtt();
/** @type {import("./$types").RequestHandler} */ /** @type {import("./$types").RequestHandler} */
@ -186,10 +160,8 @@ export async function GET({ request }) {
let isConnected = true; let isConnected = true;
let interval; let interval;
// Add this controller to the broadcast set
sseControllers.add(controller); sseControllers.add(controller);
// Cleanup function
const cleanup = () => { const cleanup = () => {
console.log("Cleaning up MQTT SSE connection"); console.log("Cleaning up MQTT SSE connection");
isConnected = false; isConnected = false;
@ -203,7 +175,6 @@ export async function GET({ request }) {
} }
}; };
// Send current device data
function sendDevices() { function sendDevices() {
if (!isConnected) { if (!isConnected) {
cleanup(); cleanup();
@ -217,12 +188,10 @@ export async function GET({ request }) {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
// Check if controller is still valid before enqueueing
if (isConnected) { if (isConnected) {
controller.enqueue(`data: ${JSON.stringify(message)}\n\n`); controller.enqueue(`data: ${JSON.stringify(message)}\n\n`);
} }
} catch (error) { } catch (error) {
// Handle controller closed error specifically
if ( if (
error.code === "ERR_INVALID_STATE" || error.code === "ERR_INVALID_STATE" ||
error.message.includes("Controller is already closed") error.message.includes("Controller is already closed")
@ -235,13 +204,10 @@ export async function GET({ request }) {
} }
} }
// Send initial data
sendDevices(); sendDevices();
// Send updates every 2 seconds
interval = setInterval(sendDevices, 2000); interval = setInterval(sendDevices, 2000);
// Subscribe to MQTT messages via the store for real-time updates
const unsubscribe = latestMessage.subscribe((message) => { const unsubscribe = latestMessage.subscribe((message) => {
if (message !== "No message yet" && isConnected) { if (message !== "No message yet" && isConnected) {
try { try {
@ -265,7 +231,6 @@ export async function GET({ request }) {
} }
}); });
// Handle client disconnection
request.signal.addEventListener("abort", () => { request.signal.addEventListener("abort", () => {
console.log("Client disconnected from MQTT SSE stream"); console.log("Client disconnected from MQTT SSE stream");
cleanup(); cleanup();