diff --git a/Server/src/lib/components/sensor-settings-form.svelte b/Server/src/lib/components/sensor-settings-form.svelte
index 22f377c..638bdef 100644
--- a/Server/src/lib/components/sensor-settings-form.svelte
+++ b/Server/src/lib/components/sensor-settings-form.svelte
@@ -24,14 +24,16 @@
export let formAction = '/sensor';
export let formMethod: 'POST' | 'PUT' = 'POST';
+ const MAX_FIELD_CAPACITY = 2500;
+
let initialConfig: SensorConfigurationDTO =
config != undefined
? { ...config }
: {
name: funnyPlantNames[Math.floor(Math.random() * funnyPlantNames.length)],
imageBase64: undefined,
- fieldCapacity: 1024,
- permanentWiltingPoint: 1024 * 0.3,
+ fieldCapacity: MAX_FIELD_CAPACITY,
+ permanentWiltingPoint: MAX_FIELD_CAPACITY * 0.3,
upperThreshold: 0.8,
lowerThreshold: 0.2
};
@@ -41,10 +43,23 @@
let sliderValues: (number | string)[] = [];
$: [permanentWiltingPoint, lowerThreshold, upperThreshold, fieldCapacity] = sliderValues.map(
- (v) => (typeof v === 'number' ? Math.floor(v) : Math.floor(parseFloat(v)))
+ (v) => Math.floor((parseFloat(v.toString()) / 100) * MAX_FIELD_CAPACITY)
);
onMount(async () => {
+ const sliderLabels = ['Lufttrocken', 'Trocken', 'Feucht', 'Nass', 'Unter Wasser'];
+ const format = {
+ to: function (value: number) {
+ value = Math.floor((value / MAX_FIELD_CAPACITY) * (sliderLabels.length - 1));
+ return sliderLabels[value];
+ },
+ from: function (value: string) {
+ if (!Number.isNaN(parseFloat(value))) {
+ return parseFloat(value);
+ }
+ return (sliderLabels.indexOf(value) / (sliderLabels.length - 1)) * MAX_FIELD_CAPACITY;
+ }
+ };
sliderOptions = {
start: [
initialConfig.permanentWiltingPoint,
@@ -53,12 +68,21 @@
initialConfig.fieldCapacity
],
connect: true,
- range: { min: [0], max: [1024] },
+ range: { min: 0, max: MAX_FIELD_CAPACITY },
+ step: MAX_FIELD_CAPACITY / ((sliderLabels.length - 1) * 5),
+ format: format,
pips: {
mode: PipsMode.Values,
- density: 3,
- values: [0, 250, 500, 750, 1000]
- }
+ format: format,
+ values: sliderLabels.map(format.from),
+ density: 5
+ },
+ tooltips: [
+ { to: () => 'Vertrocknet' },
+ { to: () => 'Braucht Wasser' },
+ { to: () => 'Zu viel Wasser' },
+ { to: () => 'Überflutet' }
+ ]
};
});
@@ -230,7 +254,7 @@
diff --git a/Server/src/lib/components/slider.svelte b/Server/src/lib/components/slider.svelte
index 2429ecb..bba1821 100644
--- a/Server/src/lib/components/slider.svelte
+++ b/Server/src/lib/components/slider.svelte
@@ -18,7 +18,9 @@
onMount(async () => {
const NoUiSlider = (await import('nouislider')).default;
slider = NoUiSlider.create(container, options);
- slider.on('update', (newValues) => {
+ slider.on('update', () => {
+ const newValues = slider.getPositions();
+
if (JSON.stringify(newValues) === JSON.stringify(values)) return;
values = newValues;
dispatch('input', { values: newValues });
diff --git a/Server/src/lib/components/water-capacity-distribution.svelte b/Server/src/lib/components/water-capacity-distribution.svelte
index 4020e8c..e051297 100644
--- a/Server/src/lib/components/water-capacity-distribution.svelte
+++ b/Server/src/lib/components/water-capacity-distribution.svelte
@@ -24,7 +24,7 @@
(bucket <= sensorConfig.lowerThreshold * sensorConfig.fieldCapacity ||
bucket >= sensorConfig.upperThreshold * sensorConfig.fieldCapacity);
- const sensorValueMax = 1024;
+ const sensorValueMax = 2500;
const distributionMap: Map = new Map();
for (
let x = 0;
diff --git a/Server/src/lib/server/repositories/SensorDataRepository.ts b/Server/src/lib/server/repositories/SensorDataRepository.ts
index ad56c4e..523fa20 100644
--- a/Server/src/lib/server/repositories/SensorDataRepository.ts
+++ b/Server/src/lib/server/repositories/SensorDataRepository.ts
@@ -1,124 +1,133 @@
-import { db } from "$lib/server/db/worker";
-import { sensorReadings } from "$lib/server/db/schema";
-import { and, eq, gte, lte, desc, sql, asc, count } from "drizzle-orm";
+import { sensorReadings } from '$lib/server/db/schema';
+import { db } from '$lib/server/db/worker';
+import { and, asc, count, desc, eq, gte, lte, sql } from 'drizzle-orm';
+
+const MAX_FIELD_CAPACITY = 2500;
+
+function invertMoisture(
+ data: typeof sensorReadings.$inferSelect
+): typeof sensorReadings.$inferSelect {
+ return {
+ ...data,
+ moisture: MAX_FIELD_CAPACITY - data.moisture
+ };
+}
export default class SensorDataRepository {
- /**
- * Add data to the database.
- * @param data The data to add.
- * @returns The id of the inserted data or throws an error if the sensor does not exist.
- */
- static async create(
- data: typeof sensorReadings.$inferInsert
- ): Promise {
- data.date = new Date();
- const insertedRecord = await db
- .insert(sensorReadings)
- .values({ ...data })
- .returning({ id: sensorReadings.id });
+ /**
+ * Add data to the database.
+ * @param data The data to add.
+ * @returns The id of the inserted data or throws an error if the sensor does not exist.
+ */
+ static async create(data: typeof sensorReadings.$inferInsert): Promise {
+ data.date = new Date();
+ const insertedRecord = await db
+ .insert(sensorReadings)
+ .values({ ...data })
+ .returning({ id: sensorReadings.id });
- return insertedRecord[0]?.id;
- }
+ return insertedRecord[0]?.id;
+ }
- /**
- * Takes a list of data points and averages them to the specified limit.
- * The average of "water", "voltage", and "duration" are calculated.
- * The average of "date" is the last date.
- * If the limit is greater than the number of data points, the original data is returned.
- * @param data The list of data to average.
- * @param limit The final number of data points.
- * @returns The averaged data.
- */
- private static dataToAverage(
- data: typeof sensorReadings.$inferSelect[],
- limit: number
- ): typeof sensorReadings.$inferSelect[] {
- if (data.length <= limit) {
- return data;
- }
- const averagedData: typeof sensorReadings.$inferSelect[] = [];
- const step = Math.floor(data.length / limit);
- for (let i = 0; i < data.length; i += step) {
- const dataSlice = data.slice(i, i + step);
+ /**
+ * Takes a list of data points and averages them to the specified limit.
+ * The average of "water", "voltage", and "duration" are calculated.
+ * The average of "date" is the last date.
+ * If the limit is greater than the number of data points, the original data is returned.
+ * @param data The list of data to average.
+ * @param limit The final number of data points.
+ * @returns The averaged data.
+ */
+ private static dataToAverage(
+ data: (typeof sensorReadings.$inferSelect)[],
+ limit: number
+ ): (typeof sensorReadings.$inferSelect)[] {
+ if (data.length <= limit) {
+ return data;
+ }
+ const averagedData: (typeof sensorReadings.$inferSelect)[] = [];
+ const step = Math.floor(data.length / limit);
+ for (let i = 0; i < data.length; i += step) {
+ const dataSlice = data.slice(i, i + step);
- // Make a copy of the latest values
- const averagedDataPoint = { ...dataSlice[dataSlice.length - 1] };
+ // Make a copy of the latest values
+ const averagedDataPoint = { ...dataSlice[dataSlice.length - 1] };
- // Calculate averages in this bucket for summable attributes
- const attributesToAverage = [
- "light",
- "voltage",
- "temperature",
- "humidity",
- "moisture",
- "moistureStabilizationTime",
- "humidityRaw",
- "temperatureRaw",
- "rssi",
- "duration",
- ] satisfies (keyof typeof dataSlice[0])[];
+ // Calculate averages in this bucket for summable attributes
+ const attributesToAverage = [
+ 'light',
+ 'voltage',
+ 'temperature',
+ 'humidity',
+ 'moisture',
+ 'moistureStabilizationTime',
+ 'humidityRaw',
+ 'temperatureRaw',
+ 'rssi',
+ 'duration'
+ ] satisfies (keyof (typeof dataSlice)[0])[];
- const calculateAverage = (property: keyof typeof dataSlice[0]) =>
- dataSlice.reduce((acc, cur) => acc + (cur[property] as number ?? 0), 0) / dataSlice.length;
+ const calculateAverage = (property: keyof (typeof dataSlice)[0]) =>
+ dataSlice.reduce((acc, cur) => acc + ((cur[property] as number) ?? 0), 0) /
+ dataSlice.length;
- for (const attribute of attributesToAverage) {
- averagedDataPoint[attribute] = calculateAverage(attribute);
- }
+ for (const attribute of attributesToAverage) {
+ averagedDataPoint[attribute] = calculateAverage(attribute);
+ }
- averagedData.push(averagedDataPoint);
- }
- return averagedData;
- }
+ averagedData.push(averagedDataPoint);
+ }
+ return averagedData;
+ }
- /**
- * Get data by sensor address. Newest data first.
- * @param sensorAddress The address of the sensor.
- * @param startDate The start date in ms since epoch. (optional)
- * @param endDate The end date in ms since epoch. (optional)
- * @param maxDataPoints The maximum number of data points. (optional)
- * @returns The data or an empty list if sensor does not exist.
- */
- static async getAllBySensorIdAveraged(
- sensorAddress: number,
- startDate: Date,
- endDate: Date,
- maxDataPoints: number
- ) {
- // Fetch data between startDate and endDate
- const data = await db
- .select()
- .from(sensorReadings)
- .where(
- and(
- eq(sensorReadings.sensorAddress, sensorAddress),
- gte(sensorReadings.date, startDate),
- lte(sensorReadings.date, endDate)
- )
- )
- .orderBy(desc(sensorReadings.date));
+ /**
+ * Get data by sensor address. Newest data first.
+ * @param sensorAddress The address of the sensor.
+ * @param startDate The start date in ms since epoch. (optional)
+ * @param endDate The end date in ms since epoch. (optional)
+ * @param maxDataPoints The maximum number of data points. (optional)
+ * @returns The data or an empty list if sensor does not exist.
+ */
+ static async getAllBySensorIdAveraged(
+ sensorAddress: number,
+ startDate: Date,
+ endDate: Date,
+ maxDataPoints: number
+ ) {
+ // Fetch data between startDate and endDate
+ const data = (
+ await db
+ .select()
+ .from(sensorReadings)
+ .where(
+ and(
+ eq(sensorReadings.sensorAddress, sensorAddress),
+ gte(sensorReadings.date, startDate),
+ lte(sensorReadings.date, endDate)
+ )
+ )
+ .orderBy(desc(sensorReadings.date))
+ ).map(invertMoisture);
- // Return the averaged data
- return this.dataToAverage(data, maxDataPoints);
- }
+ // Return the averaged data
+ return this.dataToAverage(data, maxDataPoints);
+ }
- static async getCountByWaterCapacityBucket(
- sensorId: number,
- sinceDate: Date,
- bucketSize: number
- ) {
- const dist = await db
- .select({
- count: count(),
- bucket: sql`floor(${sensorReadings.moisture} / ${bucketSize}) * ${bucketSize}`
- })
- .from(sensorReadings)
- .where(and(
- eq(sensorReadings.sensorAddress, sensorId),
- gte(sensorReadings.date, sinceDate)
- ))
- .groupBy(sensorReadings.moisture)
- .orderBy((table) => asc(table.bucket));
+ static async getCountByWaterCapacityBucket(
+ sensorId: number,
+ sinceDate: Date,
+ bucketSize: number
+ ) {
+ const dist = await db
+ .select({
+ count: count(),
+ bucket: sql`floor((${MAX_FIELD_CAPACITY} - ${sensorReadings.moisture}) / ${bucketSize}) * ${bucketSize}`
+ })
+ .from(sensorReadings)
+ .where(and(eq(sensorReadings.sensorAddress, sensorId), gte(sensorReadings.date, sinceDate)))
+ .groupBy(sensorReadings.moisture)
+ .orderBy((table) => asc(table.bucket));
- return dist;
- }
+ return dist;
+ }
}