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,
"version": "7",
"when": 1730747291671,
"tag": "20241104190811_lucia",
"when": 1749889934943,
"tag": "20250614083214_lucia",
"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 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;
}

View file

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

View file

@ -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"

View file

@ -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`);

View file

@ -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,

View file

@ -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 = {

View file

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

View file

@ -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();