dev smartfarming #1

Merged
agrilink merged 53 commits from development into main 2024-12-30 05:53:19 +00:00
6 changed files with 265 additions and 204 deletions
Showing only changes of commit 581ca5f35c - Show all commits

View File

@ -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()
]
};

View File

@ -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<number>;
humidity: number;
temperature: number;
soil_moisture: number;
dht: DHTSensor;
npk1: NPKSensor;
npk2: NPKSensor;
}

View File

@ -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) {}
get(){
return this.http.get(this.apiURL);
getSensorData(sensor: string, metric: string, startDate: string, timeRange: string): Observable<ApiResponse> {
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<ApiResponse>(url, { params });
}
getLatestData(): Observable<ApiResponse> {
const url = `${this.baseUrl}getLatest`;
return this.http.get<ApiResponse>(url);
}
}

View File

@ -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.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;
}, 1000);
},
(error) => {
console.error('Error fetching sensor data:', error);
this.isLoaded = false;
}
);
}
}

View File

@ -4,8 +4,9 @@
<option value="npk1">NPK 1</option>
<option value="npk2">NPK 2</option>
</select>
<select class="form-select" id="parameterSelect" (change)="updateChart()">
<option *ngFor="let parameter of parameters" [value]="parameter">{{ parameter }}</option>
<select class="form-select" id="parameterSelect" (change)="updateChart()" [(ngModel)]="selectedParameter">
<option *ngFor="let parameter of parameters" [value]="getParameterKey(parameter)">{{ parameter }}</option>
</select>
</div>
@ -14,5 +15,3 @@
</div>
<canvas #myChart id="myChart"></canvas>

View File

@ -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<HTMLCanvasElement>;
selectedSensor: string | null = null;
selectedSensor: string = '';
selectedParameter: string = '';
parameters: string[] = [];
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);
this.updateChart();
}
window.addEventListener('resize', () => {
ngOnDestroy(): void {
window.removeEventListener('resize', this.resizeListener);
}
onResize(): void {
if (this.chart) {
this.chart.destroy();
this.createChart(this.dhtData.temperature, 'Temperature', '#8F5A62', this.labelHourly);
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[], label: string, borderColor: string, labels: string[]): void {
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 = '';
label: (tooltipItem) => {
const paramLabel = displayName;
const value = tooltipItem.formattedValue;
return `${paramLabel}: ${value}`;
}
return `${tooltipItem.dataset.label}: ${formattedValue} ${unit}`;
},
}
},
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();
let newData: number[] | undefined;
let newLabel: string = '';
let newColor: string = '';
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 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';
const startDate = `${year}-${month}-${day}`;
const timeRange = 'HOURLY';
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.createChart(newData, newLabel, newColor, this.labelHourly);
this.isLoading = false;
}, 2000);
},
(error) => {
console.error('API Error:', error);
this.isLoading = false;
}
);
}
}