added get device and update the values live
This commit is contained in:
parent
5d689e6005
commit
91ca53880b
6 changed files with 407 additions and 32 deletions
42
src/lib/server/mqtt-devices.js
Normal file
42
src/lib/server/mqtt-devices.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
for (const [deviceId, device] of connectedDevices.entries()) {
|
||||||
|
if (device.lastSeen < fiveMinutesAgo && device.status === "online") {
|
||||||
|
connectedDevices.set(deviceId, { ...device, status: "offline" });
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export function to get current devices
|
||||||
|
export function getCurrentDevices() {
|
||||||
|
cleanupDevices();
|
||||||
|
return Array.from(connectedDevices.values()).map((device) => {
|
||||||
|
const sensorData = deviceSensorData.get(device.id);
|
||||||
|
return {
|
||||||
|
...device,
|
||||||
|
sensorData: sensorData || null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export function to get sensor data for a specific device
|
||||||
|
export function getDeviceSensorData(deviceId) {
|
||||||
|
return deviceSensorData.get(deviceId) || null;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ 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 { connect } from "mqtt";
|
||||||
|
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
|
// Convert slug to number for floor lookup
|
||||||
|
@ -18,6 +19,18 @@ export const load: PageServerLoad = async ({ params }) => {
|
||||||
try {
|
try {
|
||||||
// Try to parse the saved configuration
|
// 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) {
|
||||||
|
config.devices = config.devices.map(device => {
|
||||||
|
const sensorData = getDeviceSensorData(device.id);
|
||||||
|
return {
|
||||||
|
...device,
|
||||||
|
sensorData: sensorData
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slug: params.slug,
|
slug: params.slug,
|
||||||
floorConfig: config,
|
floorConfig: config,
|
||||||
|
|
|
@ -6,22 +6,50 @@
|
||||||
|
|
||||||
let mqttMessage = $state("Waiting for MQTT message...");
|
let mqttMessage = $state("Waiting for MQTT message...");
|
||||||
let eventSource;
|
let eventSource;
|
||||||
|
let deviceSensorData = $state(new Map());
|
||||||
|
let refreshInterval;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Connect to the SSE endpoint
|
// Connect to the SSE endpoint
|
||||||
|
console.log("Attempting to connect to MQTT SSE...");
|
||||||
eventSource = new EventSource("/mqtt");
|
eventSource = new EventSource("/mqtt");
|
||||||
|
|
||||||
eventSource.onopen = () => {
|
eventSource.onopen = () => {
|
||||||
console.log("SSE connection opened.");
|
console.log("SSE connection opened successfully!");
|
||||||
};
|
};
|
||||||
|
|
||||||
eventSource.onmessage = (event) => {
|
eventSource.onmessage = (event) => {
|
||||||
|
if (eventSource.readyState !== EventSource.OPEN) {
|
||||||
|
console.log("Floor page: Received message but connection is not open, ignoring");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const messageData = JSON.parse(event.data);
|
||||||
mqttMessage = data.message;
|
console.log("Floor page received SSE data:", messageData);
|
||||||
console.log("Received SSE message:", mqttMessage);
|
|
||||||
|
// Handle device updates
|
||||||
|
if (messageData.devices) {
|
||||||
|
console.log("Received devices:", messageData.devices);
|
||||||
|
const newData = new Map();
|
||||||
|
messageData.devices.forEach((device) => {
|
||||||
|
console.log(`Device ${device.id} has sensor data:`, device.sensorData);
|
||||||
|
if (device.sensorData) {
|
||||||
|
newData.set(device.id, device.sensorData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
deviceSensorData = newData;
|
||||||
|
console.log("Updated deviceSensorData:", deviceSensorData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle MQTT message if present
|
||||||
|
if (messageData.message) {
|
||||||
|
mqttMessage = messageData.message;
|
||||||
|
} else {
|
||||||
|
mqttMessage = `Last update: ${messageData.timestamp}`;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error parsing SSE message:", e);
|
console.error("Floor page: Error parsing SSE message:", e, "Raw data:", event.data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,9 +60,12 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (eventSource) {
|
if (eventSource && eventSource.readyState !== EventSource.CLOSED) {
|
||||||
console.log("Closing SSE connection.");
|
console.log("Floor page: Closing SSE connection");
|
||||||
eventSource.close(); // Clean up the connection when the component is destroyed
|
eventSource.close();
|
||||||
|
}
|
||||||
|
if (refreshInterval) {
|
||||||
|
clearInterval(refreshInterval);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -114,19 +145,51 @@
|
||||||
<div class="mt-2 grid grid-cols-2 gap-1">
|
<div class="mt-2 grid grid-cols-2 gap-1">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Thermometer class="h-3 w-3 text-orange-500" />
|
<Thermometer class="h-3 w-3 text-orange-500" />
|
||||||
<span class="text-xs">23°C</span>
|
<span class="text-xs">
|
||||||
|
{#if deviceSensorData.get(device.id)?.temperature !== undefined}
|
||||||
|
{deviceSensorData.get(device.id).temperature.toFixed(1)}°C
|
||||||
|
{:else if device.sensorData}
|
||||||
|
{device.sensorData.temperature.toFixed(1)}°C
|
||||||
|
{:else}
|
||||||
|
--°C
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Droplets class="h-3 w-3 text-blue-500" />
|
<Droplets class="h-3 w-3 text-blue-500" />
|
||||||
<span class="text-xs">45%</span>
|
<span class="text-xs">
|
||||||
|
{#if deviceSensorData.get(device.id)?.humidity !== undefined}
|
||||||
|
{deviceSensorData.get(device.id).humidity.toFixed(1)}%
|
||||||
|
{:else if device.sensorData}
|
||||||
|
{device.sensorData.humidity.toFixed(1)}%
|
||||||
|
{:else}
|
||||||
|
--%
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Gauge class="h-3 w-3 text-green-500" />
|
<Gauge class="h-3 w-3 text-green-500" />
|
||||||
<span class="text-xs">1013</span>
|
<span class="text-xs">
|
||||||
|
{#if deviceSensorData.get(device.id)?.pressure !== undefined}
|
||||||
|
{Math.round(deviceSensorData.get(device.id).pressure)}Pa
|
||||||
|
{:else if device.sensorData}
|
||||||
|
{Math.round(device.sensorData.pressure)}Pa
|
||||||
|
{:else}
|
||||||
|
--Pa
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Mountain class="h-3 w-3 text-purple-500" />
|
<Mountain class="h-3 w-3 text-purple-500" />
|
||||||
<span class="text-xs">120m</span>
|
<span class="text-xs">
|
||||||
|
{#if deviceSensorData.get(device.id)?.altitude !== undefined}
|
||||||
|
{deviceSensorData.get(device.id).altitude.toFixed(1)}m
|
||||||
|
{:else if device.sensorData}
|
||||||
|
{device.sensorData.altitude.toFixed(1)}m
|
||||||
|
{:else}
|
||||||
|
--m
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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 { getCurrentDevices } from "$lib/server/mqtt-devices.js";
|
||||||
import { fail } from "@sveltejs/kit";
|
import { fail } from "@sveltejs/kit";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
@ -10,13 +11,20 @@ export const load = async (event) => {
|
||||||
.from(table.floors)
|
.from(table.floors)
|
||||||
.orderBy(table.floors.floor);
|
.orderBy(table.floors.floor);
|
||||||
|
|
||||||
// Provide mock ESP8266 devices for now
|
// Get real connected devices from MQTT
|
||||||
const devices = [
|
let devices = getCurrentDevices();
|
||||||
{ id: "esp8266-001", name: "ESP8266 #001", type: "esp8266", status: "online" },
|
|
||||||
{ id: "esp8266-002", name: "ESP8266 #002", type: "esp8266", status: "online" },
|
// If no real devices, provide fallback mock devices
|
||||||
{ id: "esp8266-003", name: "ESP8266 #003", type: "esp8266", status: "offline" },
|
if (devices.length === 0) {
|
||||||
{ id: "esp8266-004", name: "ESP8266 #004", type: "esp8266", status: "online" },
|
devices = [
|
||||||
];
|
{
|
||||||
|
id: "no-devices",
|
||||||
|
name: "No ESP8266 devices connected",
|
||||||
|
type: "esp8266",
|
||||||
|
status: "offline",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
floors: floors.map((f) => f.floor),
|
floors: floors.map((f) => f.floor),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { enhance } from "$app/forms";
|
import { enhance } from "$app/forms";
|
||||||
|
import { onMount, onDestroy } from "svelte";
|
||||||
import Button from "$lib/components/ui/button/button.svelte";
|
import Button from "$lib/components/ui/button/button.svelte";
|
||||||
import Input from "$lib/components/ui/input/input.svelte";
|
import Input from "$lib/components/ui/input/input.svelte";
|
||||||
import Label from "$lib/components/ui/label/label.svelte";
|
import Label from "$lib/components/ui/label/label.svelte";
|
||||||
|
@ -14,6 +15,8 @@
|
||||||
|
|
||||||
let floorPlanImage = $state(null);
|
let floorPlanImage = $state(null);
|
||||||
let availableDevices = $state(data.devices || []);
|
let availableDevices = $state(data.devices || []);
|
||||||
|
let deviceSensorData = $state(new Map());
|
||||||
|
let eventSource;
|
||||||
|
|
||||||
let placedDevices = $state([]);
|
let placedDevices = $state([]);
|
||||||
let draggedDevice = $state(null);
|
let draggedDevice = $state(null);
|
||||||
|
@ -34,6 +37,58 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set up real-time updates via SSE
|
||||||
|
onMount(() => {
|
||||||
|
console.log("Settings: Attempting to connect to MQTT SSE...");
|
||||||
|
eventSource = new EventSource("/mqtt");
|
||||||
|
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
console.log("Settings: SSE connection opened successfully!");
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
if (eventSource.readyState !== EventSource.OPEN) {
|
||||||
|
console.log("Settings: Received message but connection is not open, ignoring");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
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) {
|
||||||
|
newData.set(device.id, device.sensorData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
deviceSensorData = newData;
|
||||||
|
console.log("Updated deviceSensorData:", deviceSensorData);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Settings: Error parsing SSE message:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onerror = (error) => {
|
||||||
|
console.error("SSE Error:", error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (eventSource && eventSource.readyState !== EventSource.CLOSED) {
|
||||||
|
console.log("Settings: Closing SSE connection");
|
||||||
|
eventSource.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function handleFileUpload(event) {
|
function handleFileUpload(event) {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
if (file && file.type.startsWith("image/")) {
|
if (file && file.type.startsWith("image/")) {
|
||||||
|
@ -269,19 +324,43 @@
|
||||||
<div class="grid grid-cols-2 gap-1">
|
<div class="grid grid-cols-2 gap-1">
|
||||||
<div class="flex items-center gap-1 text-xs">
|
<div class="flex items-center gap-1 text-xs">
|
||||||
<Thermometer class="h-3 w-3 text-orange-500" />
|
<Thermometer class="h-3 w-3 text-orange-500" />
|
||||||
<span>Temperature</span>
|
<span>
|
||||||
|
{#if device.sensorData}
|
||||||
|
{device.sensorData.temperature.toFixed(1)}°C
|
||||||
|
{:else}
|
||||||
|
--°C
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 text-xs">
|
<div class="flex items-center gap-1 text-xs">
|
||||||
<Droplets class="h-3 w-3 text-blue-500" />
|
<Droplets class="h-3 w-3 text-blue-500" />
|
||||||
<span>Humidity</span>
|
<span>
|
||||||
|
{#if device.sensorData}
|
||||||
|
{device.sensorData.humidity.toFixed(1)}%
|
||||||
|
{:else}
|
||||||
|
--%
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 text-xs">
|
<div class="flex items-center gap-1 text-xs">
|
||||||
<Gauge class="h-3 w-3 text-green-500" />
|
<Gauge class="h-3 w-3 text-green-500" />
|
||||||
<span>Pressure</span>
|
<span>
|
||||||
|
{#if device.sensorData}
|
||||||
|
{Math.round(device.sensorData.pressure)}Pa
|
||||||
|
{:else}
|
||||||
|
--Pa
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 text-xs">
|
<div class="flex items-center gap-1 text-xs">
|
||||||
<Mountain class="h-3 w-3 text-purple-500" />
|
<Mountain class="h-3 w-3 text-purple-500" />
|
||||||
<span>Altitude</span>
|
<span>
|
||||||
|
{#if device.sensorData}
|
||||||
|
{device.sensorData.altitude.toFixed(1)}m
|
||||||
|
{:else}
|
||||||
|
--m
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// src/routes/mqtt/+server.js
|
// src/routes/mqtt/+server.js
|
||||||
|
import { connectedDevices, deviceSensorData, getCurrentDevices } from "$lib/server/mqtt-devices.js";
|
||||||
import * as mqtt from "mqtt";
|
import * as mqtt from "mqtt";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
@ -10,6 +11,85 @@ const devices = writable([]);
|
||||||
|
|
||||||
let client = null;
|
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,
|
||||||
|
type: "esp8266",
|
||||||
|
status: "online",
|
||||||
|
lastSeen: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update sensor data if provided
|
||||||
|
if (sensorData) {
|
||||||
|
deviceSensorData.set(deviceId, {
|
||||||
|
...sensorData,
|
||||||
|
timestamp: now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the devices store
|
||||||
|
updateDevicesStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to parse sensor data from MQTT payload
|
||||||
|
function parseSensorData(payload) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(payload);
|
||||||
|
return {
|
||||||
|
temperature: parseFloat(data.temperature) || 0,
|
||||||
|
humidity: parseFloat(data.humidity?.replace("%", "")) || 0,
|
||||||
|
pressure: parseFloat(data.pressure) || 0,
|
||||||
|
altitude: parseFloat(data.altitude) || 0,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing sensor data:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
return {
|
||||||
|
...device,
|
||||||
|
sensorData: sensorData || null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (const [deviceId, device] of connectedDevices.entries()) {
|
||||||
|
if (device.lastSeen < fiveMinutesAgo && device.status === "online") {
|
||||||
|
connectedDevices.set(deviceId, { ...device, status: "offline" });
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
updateDevicesStore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run cleanup every minute
|
||||||
|
setInterval(cleanupDevices, 60000);
|
||||||
|
|
||||||
|
// getCurrentDevices is now imported from the shared module
|
||||||
|
|
||||||
// Function to connect to MQTT
|
// Function to connect to MQTT
|
||||||
function connectMqtt() {
|
function connectMqtt() {
|
||||||
if (client && client.connected) {
|
if (client && client.connected) {
|
||||||
|
@ -38,7 +118,23 @@ function connectMqtt() {
|
||||||
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); // Update the Svelte store
|
||||||
console.log(topic.split("/")[1]);
|
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
// Still update device as online even if data parsing failed
|
||||||
|
updateDevice(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
|
@ -71,24 +167,98 @@ export async function GET({ request }) {
|
||||||
return new Response(
|
return new Response(
|
||||||
new ReadableStream({
|
new ReadableStream({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
console.log("Client connected to SSE stream.");
|
console.log("Client connected to MQTT SSE stream");
|
||||||
|
|
||||||
|
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;
|
||||||
|
if (interval) {
|
||||||
|
clearInterval(interval);
|
||||||
|
interval = null;
|
||||||
|
}
|
||||||
|
sseControllers.delete(controller);
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send current device data
|
||||||
|
function sendDevices() {
|
||||||
|
if (!isConnected) {
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const devices = getCurrentDevices();
|
||||||
|
const message = {
|
||||||
|
devices: devices,
|
||||||
|
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")
|
||||||
|
) {
|
||||||
|
console.log("MQTT SSE controller closed, stopping interval");
|
||||||
|
} else {
|
||||||
|
console.error("Error sending MQTT device data:", error);
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) => {
|
const unsubscribe = latestMessage.subscribe((message) => {
|
||||||
if (message !== "No message yet") {
|
if (message !== "No message yet" && isConnected) {
|
||||||
const data = `data: ${JSON.stringify({ message: message })}\n\n`;
|
try {
|
||||||
controller.enqueue(data);
|
const data = `data: ${JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
devices: getCurrentDevices(),
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
})}\n\n`;
|
||||||
|
if (isConnected) {
|
||||||
|
controller.enqueue(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
error.code === "ERR_INVALID_STATE" ||
|
||||||
|
error.message.includes("Controller is already closed")
|
||||||
|
) {
|
||||||
|
console.log("MQTT SSE controller closed during message send");
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle client disconnection
|
// Handle client disconnection
|
||||||
request.signal.addEventListener("abort", () => {
|
request.signal.addEventListener("abort", () => {
|
||||||
console.log("Client disconnected from SSE stream.");
|
console.log("Client disconnected from MQTT SSE stream");
|
||||||
unsubscribe(); // Stop listening to store updates
|
cleanup();
|
||||||
controller.close();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
console.log("SSE stream cancelled.");
|
console.log("MQTT SSE stream cancelled");
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ headers },
|
{ headers },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue