From 581ca5f35cd84dc87276aa91a57f5acc7075118d Mon Sep 17 00:00:00 2001 From: Desy Ayurianti Date: Fri, 11 Oct 2024 10:45:32 +0700 Subject: [PATCH] feat(dashboard): integration API in data graph and data latest --- agrilink_vocpro/src/app/app.config.ts | 7 +- .../src/app/cores/interface/sensor-data.ts | 54 +++- .../src/app/cores/services/api.service.ts | 23 +- .../pages/dashboard/dashboard.component.ts | 80 +++-- .../dashboard/page/graph/graph.component.html | 9 +- .../dashboard/page/graph/graph.component.ts | 296 ++++++++---------- 6 files changed, 265 insertions(+), 204 deletions(-) diff --git a/agrilink_vocpro/src/app/app.config.ts b/agrilink_vocpro/src/app/app.config.ts index a1e7d6f..74f4c3b 100644 --- a/agrilink_vocpro/src/app/app.config.ts +++ b/agrilink_vocpro/src/app/app.config.ts @@ -1,8 +1,13 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideHttpClient() + ] }; diff --git a/agrilink_vocpro/src/app/cores/interface/sensor-data.ts b/agrilink_vocpro/src/app/cores/interface/sensor-data.ts index 4eeee95..f8256cd 100644 --- a/agrilink_vocpro/src/app/cores/interface/sensor-data.ts +++ b/agrilink_vocpro/src/app/cores/interface/sensor-data.ts @@ -1,8 +1,50 @@ -import { Timestamp } from "rxjs"; +export interface ParameterSensor { + hour: number; + + //for DHT sensor + vicitemperature_avg?: number; + vicihumidity_avg?: number; + viciluminosity_avg?: number; + + // for NPK1 & NPK2 sensor + soiltemperature_avg?: number; + soilhumidity_avg?: number; + soilconductivity_avg?: number; + soilph_avg?: number; + soilnitrogen_avg?: number; + soilphosphorus_avg?: number; + soilpotassium_avg?: number; +} + +export interface ApiResponse { + data: { + dht?: ParameterSensor[]; + npk1?: ParameterSensor[]; + npk2?: ParameterSensor[]; + }; + statusCode: number; + message: string; +} + +export interface DHTSensor { + lightIntensity: number; + temperature: number; + humidity: number; +} + +export interface NPKSensor { + temperature: number; + moisture: number; + conductivity: number; + ph: number; + nitrogen: number; + phosphorus: number; + potassium: number; +} export interface SensorData { - id: Timestamp; - humidity: number; - temperature: number; - soil_moisture: number; -} \ No newline at end of file + dht: DHTSensor; + npk1: NPKSensor; + npk2: NPKSensor; +} + diff --git a/agrilink_vocpro/src/app/cores/services/api.service.ts b/agrilink_vocpro/src/app/cores/services/api.service.ts index a992cae..4ae3657 100644 --- a/agrilink_vocpro/src/app/cores/services/api.service.ts +++ b/agrilink_vocpro/src/app/cores/services/api.service.ts @@ -1,16 +1,29 @@ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { ApiResponse } from '../interface/sensor-data'; @Injectable({ providedIn: 'root' }) export class ApiService { - private apiURL = 'https://jx027dj4-5000.asse.devtunnels.ms/api/sensor-data'; + private baseUrl = 'https://jx027dj4-3333.asse.devtunnels.ms/api/sensor/'; - constructor(private http: HttpClient) { } + constructor(private http: HttpClient) {} - get(){ - return this.http.get(this.apiURL); + getSensorData(sensor: string, metric: string, startDate: string, timeRange: string): Observable { + const url = `${this.baseUrl}getData`; + const params = new HttpParams() + .set('range[start]', startDate) + .set('range[time_range]', timeRange) + .set('sensor', sensor) + .set('metric', metric); + + return this.http.get(url, { params }); } + getLatestData(): Observable { + const url = `${this.baseUrl}getLatest`; + return this.http.get(url); + } } diff --git a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts index e791723..a5541d9 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts +++ b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts @@ -4,43 +4,27 @@ import { SidebarComponent } from './layouts/sidebar/sidebar.component'; import { GaugeChartComponent } from './page/gauge-chart/gauge-chart.component'; import { GraphComponent } from './page/graph/graph.component'; import { CommonModule } from '@angular/common'; +import { ApiService } from '../../cores/services/api.service'; +import { SensorData } from '../../cores/interface/sensor-data'; @Component({ selector: 'app-dashboard', standalone: true, - imports: [RouterOutlet, SidebarComponent, GaugeChartComponent, GraphComponent, CommonModule], + imports: [RouterOutlet, SidebarComponent, GaugeChartComponent, CommonModule, GraphComponent], templateUrl: './dashboard.component.html', styleUrls: ['./dashboard.component.scss'] }) export class DashboardComponent implements OnInit { isLoaded: boolean = false; selectedButton: string = ''; - sensorData: any = { - dht: { - lightIntensity: '3539', - temperature: '27', - humidity: '80' - }, - npk1: { - temperature: '28', - moisture: '99.9', - conductivity: '1.5', - ph: '8.51', - nitrogen: '500', - phosphorus: '500', - potassium: '500' - }, - npk2: { - temperature: '28.6', - moisture: '99.8', - conductivity: '1.4', - ph: '8.55', - nitrogen: '510', - phosphorus: '512', - potassium: '500' - } + sensorData: SensorData = { + dht: { lightIntensity: 0, temperature: 0, humidity: 0 }, + npk1: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 }, + npk2: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 } }; + constructor(private apiService: ApiService) {} + ngOnInit(): void { this.selectedButton = 'dht'; this.loadData(); @@ -53,8 +37,48 @@ export class DashboardComponent implements OnInit { loadData(): void { this.isLoaded = true; - setTimeout(() => { - this.isLoaded = false; - }, 1000); + + this.apiService.getLatestData().subscribe( + (response) => { + const data = response.data; + if (data.dht && data.dht.length > 0) { + this.sensorData.dht = { + lightIntensity: data.dht[0].viciluminosity_avg ?? 0, + temperature: data.dht[0].vicitemperature_avg ?? 0, + humidity: data.dht[0].vicihumidity_avg ?? 0 + }; + } + + if (data.npk1 && data.npk1.length > 0) { + this.sensorData.npk1 = { + temperature: data.npk1[0].soiltemperature_avg ?? 0, + moisture: data.npk1[0].soilhumidity_avg ?? 0, + conductivity: data.npk1[0].soilconductivity_avg ?? 0, + ph: data.npk1[0].soilph_avg ?? 0, + nitrogen: data.npk1[0].soilnitrogen_avg ?? 0, + phosphorus: data.npk1[0].soilphosphorus_avg ?? 0, + potassium: data.npk1[0].soilpotassium_avg ?? 0 + }; + } + + if (data.npk2 && data.npk2.length > 0) { + this.sensorData.npk2 = { + temperature: data.npk2[0].soiltemperature_avg ?? 0, + moisture: data.npk2[0].soilhumidity_avg ?? 0, + conductivity: data.npk2[0].soilconductivity_avg ?? 0, + ph: data.npk2[0].soilph_avg ?? 0, + nitrogen: data.npk2[0].soilnitrogen_avg ?? 0, + phosphorus: data.npk2[0].soilphosphorus_avg ?? 0, + potassium: data.npk2[0].soilpotassium_avg ?? 0 + }; + } + + this.isLoaded = false; + }, + (error) => { + console.error('Error fetching sensor data:', error); + this.isLoaded = false; + } + ); } } diff --git a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html index 607fd27..14671f9 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html +++ b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html @@ -4,9 +4,10 @@ - + +
@@ -14,5 +15,3 @@
- - diff --git a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts index 27484b5..8a074c0 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts +++ b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts @@ -1,81 +1,107 @@ +import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; +import { Chart, registerables } from 'chart.js'; +import { ApiService } from '../../../../cores/services/api.service'; +import { ApiResponse, ParameterSensor} from '../../../../cores/interface/sensor-data'; import { CommonModule } from '@angular/common'; -import { Component, OnInit, ElementRef, ViewChild } from '@angular/core'; -import { Chart, LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, Tooltip, Filler } from 'chart.js'; +import { FormsModule } from '@angular/forms'; + +Chart.register(...registerables); +const parameterColors: { [key: string]: string } = { + vicitemperature_avg: '#8F5A62', + vicihumidity_avg: '#16423C', + viciluminosity_avg: '#DF9B55', + soiltemperature_avg: '#8F5A62', + soilhumidity_avg: '#54909c', + soilconductivity_avg: '#661311', + soilph_avg: '#664735', + soilnitrogen_avg: '#3a6635', + soilphosphorus_avg: '#3f3566', + soilpotassium_avg: '#5f3566', +}; + @Component({ selector: 'app-graph', standalone: true, - imports: [CommonModule], + imports: [CommonModule, FormsModule], templateUrl: './graph.component.html', styleUrls: ['./graph.component.scss'] }) -export class GraphComponent implements OnInit { +export class GraphComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('myChart', { static: false }) chartElement!: ElementRef; - selectedSensor: string | null = null; + selectedSensor: string = ''; + selectedParameter: string = ''; parameters: string[] = []; - isLoading: boolean = true; + isLoading: boolean = true; sensorParameters: { [key: string]: string[] } = { - dht: ['Temperature', 'Humidity', 'Light'], - npk1: ['Temperature', 'Moisture', 'Conductivity', 'ph', 'Nitrogen', 'Phosphorus', 'Potassium'], - npk2: ['Temperature', 'Moisture', 'Conductivity', 'ph', 'Nitrogen', 'Phosphorus', 'Potassium'] + dht: ['vicitemperature_avg', 'vicihumidity_avg', 'viciluminosity_avg'], + npk1: ['soiltemperature_avg', 'soilhumidity_avg', 'soilconductivity_avg', 'soilph_avg', 'soilnitrogen_avg', 'soilphosphorus_avg', 'soilpotassium_avg'], + npk2: ['soiltemperature_avg', 'soilhumidity_avg', 'soilconductivity_avg', 'soilph_avg', 'soilnitrogen_avg', 'soilphosphorus_avg', 'soilpotassium_avg'], }; - dhtData = { - humidity: [65, 59, 80, 81, 56, 55, 40, 41, 60, 65, 59, 80, 81, 56, 55, 40, 41, 60, 65, 59, 80, 81, 56, 55], - temperature: [22, 23, 25, 24, 26, 27, 28, 29, 25, 23, 22, 24, 26, 27, 28, 29, 29, 28, 28, 25, 25, 25, 26, 27], - light: [200, 220, 230, 210, 250, 240, 260, 265, 270, 200, 220, 230, 210, 250, 240, 260, 265, 270, 275, 280, 285, 290, 295, 300] + parameterDisplayNames: { [key: string]: string } = { + vicitemperature_avg: 'Temperature (°C)', + vicihumidity_avg: 'Humidity (%)', + viciluminosity_avg: 'Luminosity (lx)', + soiltemperature_avg: 'Soil Temperature (°C)', + soilhumidity_avg: 'Soil Humidity (%)', + soilconductivity_avg: 'Conductivity (mS/cm)', + soilph_avg: 'pH', + soilnitrogen_avg: 'Nitrogen (PPM)', + soilphosphorus_avg: 'Phosphorus (PPM)', + soilpotassium_avg: 'Potassium (PPM)', }; - npk1Data = { - temperature: [23, 24, 26, 27, 28, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 23, 24, 26, 27, 28, 29, 28, 27], - moisture: [30, 35, 32, 36, 34, 37, 33, 31, 32, 30, 35, 32, 36, 34, 37, 33, 31, 32, 30, 35, 32, 36, 34, 37], - conductivity: [500, 550, 600, 520, 510, 530, 540, 580, 590, 500, 550, 600, 520, 510, 530, 540, 580, 590, 500, 550, 600, 520, 510, 530], - ph: [6.5, 6.7, 6.8, 7.0, 7.1, 6.9, 6.6, 6.8, 6.9, 6.5, 6.7, 6.8, 7.0, 7.1, 6.9, 6.6, 6.8, 6.9, 6.5, 6.7, 6.8, 7.0, 7.1, 6.9], - nitrogen: [300, 310, 320, 330, 340, 350, 355, 360, 365, 300, 310, 320, 330, 340, 350, 355, 360, 365, 300, 310, 320, 330, 340, 350], - phosphorus: [200, 210, 215, 220, 225, 220, 215, 210, 200, 210, 215, 220, 225, 220, 215, 210, 200, 210, 215, 220, 225, 220, 215, 210], - potassium: [180, 190, 200, 205, 210, 215, 220, 225, 230, 180, 190, 200, 205, 210, 215, 220, 225, 230, 180, 190, 200, 205, 210, 215], - }; + chart: Chart | undefined; + labelsHourly: string[] = []; + private resizeListener!: () => void; - npk2Data = { - temperature: [24, 25, 27, 28, 29, 28, 26, 25, 24, 23, 22, 21, 20, 19, 23, 24, 26, 27, 28, 29, 28, 26, 25, 24], - moisture: [31, 36, 33, 38, 35, 37, 34, 32, 33, 31, 36, 33, 38, 35, 37, 34, 32, 33, 31, 36, 33, 38, 35, 37], - conductivity: [520, 530, 540, 510, 550, 570, 580, 590, 600, 520, 530, 540, 510, 550, 570, 580, 590, 600, 520, 530, 540, 510, 550, 570], - ph: [6.6, 6.9, 7.1, 6.7, 6.8, 6.9, 6.6, 6.8, 6.9, 6.6, 6.9, 7.1, 6.7, 6.8, 6.9, 6.6, 6.8, 6.9, 6.6, 6.9, 7.1, 6.7, 6.8, 6.9], - nitrogen: [320, 330, 340, 350, 360, 400, 410, 420, 430, 320, 330, 340, 350, 360, 400, 410, 420, 430, 320, 330, 340, 350, 360, 400], - phosphorus: [210, 220, 225, 230, 235, 240, 245, 250, 255, 210, 220, 225, 230, 235, 240, 245, 250, 255, 210, 220, 225, 230, 235, 240], - potassium: [190, 195, 205, 210, 215, 220, 225, 230, 235, 190, 195, 205, 210, 215, 220, 225, 230, 235, 190, 195, 205, 210, 215, 220] - }; - - chart: any; - labelHourly = [ - '01.00', '02.00', '03.00', '04.00', '05.00', '06.00', '07.00', '08.00', - '09.00', '10.00', '11.00', '12.00', '13.00', '14.00', '15.00', '16.00', - '17.00', '18.00', '19.00', '20.00', '21.00', '22.00', '23.00', '24.00' - ]; + constructor(private sensorService: ApiService) {} ngOnInit(): void { - Chart.register(LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, Tooltip, Filler); this.selectedSensor = 'dht'; - this.parameters = this.sensorParameters[this.selectedSensor]; + this.updateParameters(); + this.selectedParameter = 'vicitemperature_avg'; + this.updateChart(); + this.resizeListener = this.onResize.bind(this); + window.addEventListener('resize', this.resizeListener); } - + ngAfterViewInit(): void { - this.isLoading = true; - setTimeout(() => { - this.createChart(this.dhtData.temperature, 'Temperature', '#8F5A62', this.labelHourly); - this.isLoading = false; - }, 2000); - - window.addEventListener('resize', () => { - if (this.chart) { - this.chart.destroy(); - this.createChart(this.dhtData.temperature, 'Temperature', '#8F5A62', this.labelHourly); - } - }); + this.updateChart(); } - - createChart(data: number[], label: string, borderColor: string, labels: string[]): void { + ngOnDestroy(): void { + window.removeEventListener('resize', this.resizeListener); + } + + onResize(): void { + if (this.chart) { + this.chart.destroy(); + this.updateChart(); + } + } + + getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } { + const sensorData = response.data[sensor as keyof typeof response.data]; + + const data: number[] = []; + const labels: string[] = []; + + if (sensorData) { + sensorData.forEach(item => { + data.push(item[parameter as keyof ParameterSensor] ?? 0); + labels.push(`${item.hour}.00`); + }); + } else { + console.error('No data found for sensor:', sensor); + } + + return { data, labels }; + } + + + createChart(data: number[], parameter: string, labels: string[]): void { const canvas = this.chartElement.nativeElement; const ctx = canvas.getContext('2d'); @@ -84,17 +110,21 @@ export class GraphComponent implements OnInit { } if (ctx) { + const displayName = this.parameterDisplayNames[parameter] || parameter; + const borderColor = parameterColors[parameter] || '#000000'; + const backgroundColor = `${borderColor}4D`; + this.chart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ - label, + label: displayName, data, borderColor, borderWidth: 1.5, fill: true, - backgroundColor: borderColor + '4D', + backgroundColor, tension: 0.5, pointRadius: 0, pointHoverRadius: 0, @@ -110,124 +140,72 @@ export class GraphComponent implements OnInit { mode: 'nearest', intersect: false, callbacks: { - label: (tooltipItem: any) => { - const datasetLabel = tooltipItem.dataset.label.toLowerCase(); - const formattedValue = tooltipItem.formattedValue; - - let unit = ''; - switch (datasetLabel) { - case 'temperature': - unit = '°C'; - break; - case 'light': - unit = 'lux'; - break; - case 'moisture': - case 'humidity': - unit = '%'; - break; - case 'conductivity': - unit = 'mS/cm'; - break; - case 'nitrogen': - case 'phosphorus': - case 'potassium': - unit = 'NPM'; - break; - default: - unit = ''; - } - return `${tooltipItem.dataset.label}: ${formattedValue} ${unit}`; - }, - } + label: (tooltipItem) => { + const paramLabel = displayName; + const value = tooltipItem.formattedValue; + return `${paramLabel}: ${value}`; + } + } }, - legend: { - display: false - } + legend: { display: false } }, scales: { - x: { - grid:{ - display: false - }, - beginAtZero: true - }, - y: { - grid:{ - display: false - }, - beginAtZero: true - } + x: { grid: { display: false }, beginAtZero: true }, + y: { grid: { display: false }, beginAtZero: true } } } }); } } + + + getParameterKey(displayName: string): string { + return Object.keys(this.parameterDisplayNames).find(key => this.parameterDisplayNames[key] === displayName) || ''; + } onSensorChange(event: Event): void { - const selectElement = event.target as HTMLSelectElement; - this.selectedSensor = selectElement.value; - this.parameters = this.sensorParameters[this.selectedSensor]; - - const defaultParameter = this.parameters[0]; - (document.getElementById('parameterSelect') as HTMLSelectElement).value = defaultParameter; - + const select = event.target as HTMLSelectElement; + this.selectedSensor = select.value; + this.updateParameters(); + if (this.selectedSensor === 'dht') { + this.selectedParameter = 'vicitemperature_avg'; + } else if (this.selectedSensor === 'npk1' || this.selectedSensor === 'npk2') { + this.selectedParameter = 'soiltemperature_avg'; + } this.updateChart(); } + + updateParameters(): void { + this.parameters = this.sensorParameters[this.selectedSensor].map(param => this.parameterDisplayNames[param]); + this.selectedParameter = this.parameters[1]; + } updateChart(): void { - this.isLoading= true; - setTimeout(() => { - const parameter = (document.getElementById('parameterSelect') as HTMLSelectElement).value.toLowerCase(); + this.isLoading = true; + + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, '0'); + const day = String(today.getDate()).padStart(2, '0'); + + const startDate = `${year}-${month}-${day}`; + const timeRange = 'HOURLY'; - let newData: number[] | undefined; - let newLabel: string = ''; - let newColor: string = ''; - - const humidity = '#16423C'; - const temperature = '#8F5A62'; - const light = '#DF9B55'; - const moisture = '#54909c'; - const conductivity = '#661311'; - const ph = '#664735'; - const nitrogen = '#3a6635'; - const phosphorus = '#3f3566'; - const potassium = '#5f3566'; - - switch (this.selectedSensor) { - case 'dht': - newData = this.dhtData[parameter as keyof typeof this.dhtData]; - newLabel = parameter.charAt(0).toUpperCase() + parameter.slice(1); - newColor = (parameter === 'humidity') ? humidity : - (parameter === 'temperature') ? temperature : light; - break; - case 'npk1': - newData = this.npk1Data[parameter as keyof typeof this.npk1Data]; - newLabel = parameter.charAt(0).toUpperCase() + parameter.slice(1); - newColor = (parameter === 'temperature') ? temperature : - (parameter === 'moisture') ? moisture : - (parameter === 'conductivity') ? conductivity : - (parameter === 'ph') ? ph : - (parameter === 'nitrogen') ? nitrogen : - (parameter === 'phosphorus') ? phosphorus : potassium; - break; - case 'npk2': - newData = this.npk2Data[parameter as keyof typeof this.npk2Data]; - newLabel = parameter.charAt(0).toUpperCase() + parameter.slice(1); - newColor = (parameter === 'temperature') ? temperature : - (parameter === 'moisture') ? moisture : - (parameter === 'conductivity') ? conductivity : - (parameter === 'ph') ? ph : - (parameter === 'nitrogen') ? nitrogen : - (parameter === 'phosphorus') ? phosphorus : potassium; - break; - default: - console.error('Sensor tidak ditemukan'); - return; + this.sensorService.getSensorData(this.selectedSensor, this.selectedParameter, startDate, timeRange).subscribe( + (response: ApiResponse) => { + if (response.statusCode === 200) { + const { data, labels } = this.getDataFromResponse(response, this.selectedSensor, this.selectedParameter); + this.createChart(data, this.selectedParameter, labels); + } else { + console.error('Error fetching data:', response.message); + } + this.isLoading = false; + }, + (error) => { + console.error('API Error:', error); + this.isLoading = false; } - this.createChart(newData, newLabel, newColor, this.labelHourly); - this.isLoading=false; - }, 2000); + ); } }