diff --git a/package.json b/package.json
index 75383b6..fb9ac4e 100644
--- a/package.json
+++ b/package.json
@@ -68,6 +68,8 @@
"@node-rs/argon2": "^2.0.2",
"@oslojs/crypto": "^1.0.1",
"@oslojs/encoding": "^1.1.0",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.2.0",
"drizzle-orm": "^0.43.1",
"mqtt": "^5.13.1",
"postgres": "^3.4.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7918f60..c8adcf8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,12 @@ importers:
'@oslojs/encoding':
specifier: ^1.1.0
version: 1.1.0
+ d3-scale:
+ specifier: ^4.0.2
+ version: 4.0.2
+ d3-shape:
+ specifier: ^3.2.0
+ version: 3.2.0
drizzle-orm:
specifier: ^0.43.1
version: 0.43.1(postgres@3.4.5)
diff --git a/src/lib/server/db/schema.js b/src/lib/server/db/schema.js
index 0ae02ed..e048caa 100644
--- a/src/lib/server/db/schema.js
+++ b/src/lib/server/db/schema.js
@@ -34,7 +34,7 @@ export const sensors = pgTable("sensors", {
});
export const sensorData = pgTable("sensor_data", {
- uuid: uuid().primaryKey(),
+ uuid: uuid().primaryKey().defaultRandom(),
sensor: text("sensor")
.references(() => sensors.id)
.notNull(),
@@ -42,4 +42,5 @@ export const sensorData = pgTable("sensor_data", {
humidity: real().notNull(),
pressure: real().notNull(),
altitude: real().notNull(),
+ time: timestamp({ withTimezone: true, mode: "date" }).notNull().defaultNow(),
});
diff --git a/src/routes/(app)/[slug]/+page.svelte b/src/routes/(app)/[slug]/+page.svelte
index 3aa8411..6491725 100644
--- a/src/routes/(app)/[slug]/+page.svelte
+++ b/src/routes/(app)/[slug]/+page.svelte
@@ -1,6 +1,10 @@
+
Floor {data.slug}
{#if data.hasConfig && data.floorConfig}
@@ -209,4 +333,267 @@
{/if}
+
+ Statistics
+
+
+
+ Statistics for floor {data.slug}
+ Showing total data for the last 3 months
+
+
+
+
+
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+
+
+ {#each sensorOptions as sensor}
+
+ {sensor.label}
+
+ {/each}
+
+
+
+
+
+
+ {selectedLabel}
+
+
+ Last 3 months
+ Last 30 days
+ Last 7 days
+
+
+
+
+
+
+ {#if isLoading}
+
+
+
+
Loading statistics...
+
+
+ {:else if hasError}
+
+
+
Error loading statistics
+
Please try again later
+
+
+ {:else if chartData.length === 0}
+
+
+
No data available
+
+ Statistics will appear once sensor data is collected
+
+
+
+ {:else}
+
+
+ {selectedSensorConfig.label}
+
+
+
+ {#if filteredData.length > 0}
+
+
+
+
Latest
+
+ {filteredData[filteredData.length - 1]?.[selectedSensor]?.toFixed(1)}
+ {#if selectedSensor === "temperature"}°C
+ {:else if selectedSensor === "humidity"}%
+ {:else if selectedSensor === "pressure"}kPa
+ {:else if selectedSensor === "altitude"}m
+ {/if}
+
+
+
+
Average
+
+ {(
+ filteredData.reduce((sum, item) => sum + item[selectedSensor], 0) /
+ filteredData.length
+ ).toFixed(1)}
+ {#if selectedSensor === "temperature"}°C
+ {:else if selectedSensor === "humidity"}%
+ {:else if selectedSensor === "pressure"}kPa
+ {:else if selectedSensor === "altitude"}m
+ {/if}
+
+
+
+
Range
+
+ {Math.min(...filteredData.map((d) => d[selectedSensor])).toFixed(1)} - {Math.max(
+ ...filteredData.map((d) => d[selectedSensor]),
+ ).toFixed(1)}
+ {#if selectedSensor === "temperature"}°C
+ {:else if selectedSensor === "humidity"}%
+ {:else if selectedSensor === "pressure"}kPa
+ {:else if selectedSensor === "altitude"}m
+ {/if}
+
+
+
+
+
+
+
+
+
+
+ Data from {filteredData[0]?.date?.toLocaleDateString()} to {filteredData[
+ filteredData.length - 1
+ ]?.date?.toLocaleDateString()}
+
+ {:else}
+
+
+
No data available
+
+ No data found for the selected time period
+
+
+
+ {/if}
+
+
+
+ {/if}
+
+
+
+
diff --git a/src/routes/(app)/[slug]/stats/+server.ts b/src/routes/(app)/[slug]/stats/+server.ts
new file mode 100644
index 0000000..fc68394
--- /dev/null
+++ b/src/routes/(app)/[slug]/stats/+server.ts
@@ -0,0 +1,28 @@
+import { db } from "$lib/server/db";
+import * as table from "$lib/server/db/schema"
+
+export const GET = async () => {
+ // Get all sensor data (like the original working version)
+ const rawData = await db.select({
+ altitude: table.sensorData.altitude,
+ humidity: table.sensorData.humidity,
+ pressure: table.sensorData.pressure,
+ temperature: table.sensorData.temperature,
+ date: table.sensorData.time,
+ }).from(table.sensorData);
+
+ // 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
+ }));
+
+ console.log(`Returning ${data.length} data points`);
+
+ return new Response(JSON.stringify(data), {
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+}
diff --git a/src/routes/(app)/settings/+page.server.js b/src/routes/(app)/settings/+page.server.js
index e7bdcb0..1939028 100644
--- a/src/routes/(app)/settings/+page.server.js
+++ b/src/routes/(app)/settings/+page.server.js
@@ -81,6 +81,9 @@ export const actions = {
try {
const deviceData = JSON.parse(devices);
+ deviceData.forEach(async (dev) => {
+ await db.insert(table.sensors).values({ id: dev.id, user: event.locals.session.userId });
+ });
// Check if floor exists
const exists = await db
diff --git a/src/routes/mqtt/+server.js b/src/routes/mqtt/+server.js
index d705f2a..75ae0aa 100644
--- a/src/routes/mqtt/+server.js
+++ b/src/routes/mqtt/+server.js
@@ -1,6 +1,9 @@
// 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";
-import * as mqtt from "mqtt";
+import { eq } from "drizzle-orm";
+import mqtt from "mqtt";
import { writable } from "svelte/store";
// A Svelte store to hold the latest MQTT message.
@@ -114,7 +117,7 @@ function connectMqtt() {
});
});
- client.on("message", (topic, message) => {
+ 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
@@ -130,6 +133,17 @@ function connectMqtt() {
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,
+ });
} else {
// Still update device as online even if data parsing failed
updateDevice(deviceId);