From fb5dba501987a580f81a1feea12986ac4bd0a6d1 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:11:42 -0800 Subject: [PATCH] fix: use correct Pulse API endpoints (/all-devices, data-range) --- backend/src/services/pulse.service.ts | 145 ++++++++++++++++++-------- 1 file changed, 101 insertions(+), 44 deletions(-) diff --git a/backend/src/services/pulse.service.ts b/backend/src/services/pulse.service.ts index 16012fa..776b364 100644 --- a/backend/src/services/pulse.service.ts +++ b/backend/src/services/pulse.service.ts @@ -21,8 +21,8 @@ export interface PulseReading { humidity: number; // % vpd: number; // kPa dewpoint: number; // Fahrenheit - light?: number; // PPFD (for Pro devices) - co2?: number; // ppm (for Pro devices) + light?: number; // lux + co2?: number; // ppm timestamp: Date; } @@ -49,41 +49,73 @@ export class PulseService { return res.json(); } + /** + * Get all devices with their latest readings + * Uses /all-devices which returns deviceViewDtos with mostRecentDataPoint + */ + async getAllDevicesWithReadings(): Promise<{ devices: PulseDevice[]; readings: PulseReading[] }> { + const data = await this.fetch('/all-devices'); + + const devices: PulseDevice[] = []; + const readings: PulseReading[] = []; + + // Process device view DTOs + for (const d of (data.deviceViewDtos || [])) { + const deviceId = String(d.id || d.deviceId); + const deviceName = d.name || 'Unknown'; + + devices.push({ + id: deviceId, + name: deviceName, + type: this.getDeviceType(d.deviceType), + isOnline: d.mostRecentDataPoint?.pluggedIn ?? true, + }); + + if (d.mostRecentDataPoint) { + const dp = d.mostRecentDataPoint; + readings.push({ + deviceId, + deviceName, + temperature: dp.temperatureF ?? 0, + humidity: dp.humidityRh ?? 0, + vpd: dp.vpd ?? 0, + dewpoint: dp.dpF ?? this.calculateDewpoint(dp.temperatureF, dp.humidityRh), + light: dp.lightLux || undefined, + co2: dp.co2 || undefined, + timestamp: new Date(dp.createdAt || Date.now()), + }); + } + } + + // Process universal sensor views if present + for (const s of (data.universalSensorViews || [])) { + const deviceId = String(s.id || s.deviceId); + const deviceName = s.name || 'Unknown Sensor'; + + devices.push({ + id: deviceId, + name: deviceName, + type: 'universal', + isOnline: true, + }); + } + + return { devices, readings }; + } + /** * Get all devices for this grow */ async getDevices(): Promise { - const data = await this.fetch('/devices'); - return (data.devices || data || []).map((d: any) => ({ - id: d.id || d.deviceId, - name: d.name || d.deviceName || 'Unknown', - type: d.type || 'pulse', - isOnline: d.isOnline ?? d.online ?? true, - })); + const { devices } = await this.getAllDevicesWithReadings(); + return devices; } /** * Get current readings for all devices */ async getCurrentReadings(): Promise { - const devices = await this.getDevices(); - const readings: PulseReading[] = []; - - for (const device of devices) { - try { - const reading = await this.getDeviceReading(device.id); - if (reading) { - readings.push({ - deviceId: device.id, - deviceName: device.name, - ...reading, - }); - } - } catch (error) { - console.warn(`Failed to get reading for ${device.name}:`, error); - } - } - + const { readings } = await this.getAllDevicesWithReadings(); return readings; } @@ -92,16 +124,16 @@ export class PulseService { */ async getDeviceReading(deviceId: string): Promise | null> { try { - const data = await this.fetch(`/devices/${deviceId}/sensors/current`); + const data = await this.fetch(`/devices/${deviceId}/recent-data`); return { - temperature: data.temperature?.value ?? data.temp ?? 0, - humidity: data.humidity?.value ?? data.rh ?? 0, - vpd: data.vpd?.value ?? data.vpd ?? 0, - dewpoint: data.dewpoint?.value ?? data.dew ?? 0, - light: data.ppfd?.value ?? data.light, - co2: data.co2?.value ?? data.co2, - timestamp: new Date(data.timestamp || Date.now()), + temperature: data.temperatureF ?? 0, + humidity: data.humidityRh ?? 0, + vpd: data.vpd ?? 0, + dewpoint: data.dpF ?? this.calculateDewpoint(data.temperatureF, data.humidityRh), + light: data.lightLux || undefined, + co2: data.co2 || undefined, + timestamp: new Date(data.createdAt || Date.now()), }; } catch { return null; @@ -113,18 +145,18 @@ export class PulseService { */ async getHistory(deviceId: string, hours: number = 24): Promise { const start = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString(); - const data = await this.fetch(`/devices/${deviceId}/sensors/data?start=${start}`); + const data = await this.fetch(`/devices/${deviceId}/data-range?start=${start}`); - return (data.readings || data || []).map((r: any) => ({ + return (data || []).map((r: any) => ({ deviceId, deviceName: '', - temperature: r.temperature ?? r.temp ?? 0, - humidity: r.humidity ?? r.rh ?? 0, + temperature: r.temperatureF ?? 0, + humidity: r.humidityRh ?? 0, vpd: r.vpd ?? 0, - dewpoint: r.dewpoint ?? r.dew ?? 0, - light: r.ppfd ?? r.light, - co2: r.co2, - timestamp: new Date(r.timestamp), + dewpoint: r.dpF ?? this.calculateDewpoint(r.temperatureF, r.humidityRh), + light: r.lightLux || undefined, + co2: r.co2 || undefined, + timestamp: new Date(r.createdAt), })); } @@ -133,12 +165,37 @@ export class PulseService { */ async testConnection(): Promise<{ success: boolean; deviceCount: number; error?: string }> { try { - const devices = await this.getDevices(); + const { devices } = await this.getAllDevicesWithReadings(); return { success: true, deviceCount: devices.length }; } catch (error: any) { return { success: false, deviceCount: 0, error: error.message }; } } + + /** + * Map device type number to string + */ + private getDeviceType(type: number): string { + const types: Record = { + 0: 'Pulse One', + 1: 'Pulse Pro', + 2: 'Pulse Hub', + }; + return types[type] || 'Pulse'; + } + + /** + * Calculate dewpoint from temperature and humidity + */ + private calculateDewpoint(tempF: number, humidity: number): number { + if (!tempF || !humidity) return 0; + const tempC = (tempF - 32) * 5 / 9; + const a = 17.27; + const b = 237.7; + const alpha = ((a * tempC) / (b + tempC)) + Math.log(humidity / 100); + const dewpointC = (b * alpha) / (a - alpha); + return (dewpointC * 9 / 5) + 32; + } } // Singleton instance (initialized with API key from env)