πͺππ½πΎ π¨ππ π₯ππ πΎ sensor-data.js
πͺππ½πΎ π¨ππ π₯ππ πΎ sensor-data.js
π‘πΊπππ π»ππΊπππΊπ πππ½πΎ πππ πΏππ πΎ sensor-data.js
FarmerSmartAI - File JavaScript Data Sensor
Berikut kode lengkap untuk file sensor-data.js yang berisi fungsi-fungsi khusus untuk manajemen data sensor IoT FarmerSmartAI:
```javascript
// sensor-data.js - JavaScript Manajemen Data Sensor FarmerSmartAI
class SensorDataManager {
constructor() {
this.sensors = new Map();
this.historicalData = new Map();
this.realTimeConnections = new Map();
this.dataBuffer = [];
this.isCollecting = false;
this.samplingRate = 5000; // 5 seconds
this.maxBufferSize = 1000;
this.alertThresholds = {
soil_moisture: { min: 40, max: 80 },
temperature: { min: 20, max: 35 },
humidity: { min: 40, max: 85 },
ph_level: { min: 5.5, max: 7.5 },
nutrient_n: { min: 30, max: 60 },
nutrient_p: { min: 20, max: 40 },
nutrient_k: { min: 25, max: 50 }
};
this.init();
}
// ===== INITIALIZATION =====
init() {
this.initializeSensors();
this.setupWebSocketConnection();
this.setupDataProcessing();
this.setupAlertSystem();
this.loadHistoricalData();
console.log('Sensor Data Manager initialized');
}
// ===== SENSOR REGISTRATION =====
initializeSensors() {
// Register all IoT sensors with their metadata
const sensorDefinitions = [
{
id: 'sensor-moisture-001',
name: 'Soil Moisture Sensor #1',
type: 'soil_moisture',
location: { lat: -7.2506, lng: 112.7685, area: 'Area Utara' },
unit: '%',
calibration: { offset: 0, factor: 1.0 },
status: 'online',
lastReading: null,
battery: 85
},
{
id: 'sensor-temperature-001',
name: 'Temperature Sensor #1',
type: 'temperature',
location: { lat: -7.2503, lng: 112.7690, area: 'Area Timur' },
unit: '°C',
calibration: { offset: 0, factor: 1.0 },
status: 'online',
lastReading: null,
battery: 92
},
{
id: 'sensor-humidity-001',
name: 'Humidity Sensor #1',
type: 'humidity',
location: { lat: -7.2500, lng: 112.7688, area: 'Area Selatan' },
unit: '%',
calibration: { offset: 0, factor: 1.0 },
status: 'online',
lastReading: null,
battery: 78
},
{
id: 'sensor-ph-001',
name: 'pH Sensor #1',
type: 'ph_level',
location: { lat: -7.2508, lng: 112.7682, area: 'Area Barat' },
unit: 'pH',
calibration: { offset: 0, factor: 1.0 },
status: 'online',
lastReading: null,
battery: 65
},
{
id: 'sensor-nutrient-001',
name: 'Nutrient Sensor #1',
type: 'nutrient_n',
location: { lat: -7.2505, lng: 112.7692, area: 'Area Tengah' },
unit: 'ppm',
calibration: { offset: 0, factor: 1.0 },
status: 'online',
lastReading: null,
battery: 88
}
];
sensorDefinitions.forEach(sensor => {
this.sensors.set(sensor.id, sensor);
this.historicalData.set(sensor.id, []);
});
}
// ===== WEB SOCKET CONNECTION =====
setupWebSocketConnection() {
// Simulate WebSocket connection for real-time data
this.simulateWebSocketData();
// In a real implementation, this would connect to actual IoT WebSocket
/*
this.ws = new WebSocket('wss://farmersmartai.com/sensors/ws');
this.ws.onopen = () => {
console.log('WebSocket connected to sensor network');
this.broadcastSensorStatus('connected');
};
this.ws.onmessage = (event) => {
this.processSensorData(JSON.parse(event.data));
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.broadcastSensorStatus('disconnected');
// Attempt reconnection
setTimeout(() => this.setupWebSocketConnection(), 5000);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
*/
}
simulateWebSocketData() {
// Simulate real-time sensor data updates
setInterval(() => {
this.sensors.forEach((sensor, sensorId) => {
if (sensor.status === 'online') {
const reading = this.generateSensorReading(sensor.type, sensorId);
this.processSensorData({
sensor_id: sensorId,
timestamp: new Date().toISOString(),
value: reading.value,
unit: sensor.unit,
battery: sensor.battery - Math.random() * 0.1,
signal_strength: this.randomInt(75, 95)
});
}
});
}, this.samplingRate);
// Simulate occasional sensor status changes
setInterval(() => {
this.simulateSensorStatusChange();
}, 30000);
}
// ===== DATA PROCESSING =====
setupDataProcessing() {
// Process buffered data every 10 seconds
setInterval(() => {
this.processBufferedData();
}, 10000);
// Clean old historical data every hour
setInterval(() => {
this.cleanHistoricalData();
}, 3600000);
}
processSensorData(data) {
try {
// Apply sensor calibration
const calibratedValue = this.applyCalibration(data);
data.calibrated_value = calibratedValue;
// Validate data quality
if (!this.validateDataQuality(data)) {
console.warn(`Low quality data from sensor ${data.sensor_id}`);
data.quality = 'low';
} else {
data.quality = 'high';
}
// Check for anomalies
this.checkDataAnomalies(data);
// Add to buffer
this.bufferData(data);
// Update sensor last reading
this.updateSensorReading(data);
// Check alert thresholds
this.checkThresholdAlerts(data);
// Broadcast to connected clients
this.broadcastDataUpdate(data);
} catch (error) {
console.error('Error processing sensor data:', error, data);
}
}
applyCalibration(data) {
const sensor = this.sensors.get(data.sensor_id);
if (!sensor || !sensor.calibration) return data.value;
const calibrated = (data.value + sensor.calibration.offset) * sensor.calibration.factor;
return Math.round(calibrated * 100) / 100; // Round to 2 decimal places
}
validateDataQuality(data) {
const sensor = this.sensors.get(data.sensor_id);
if (!sensor) return false;
// Check if value is within reasonable range for sensor type
const reasonableRanges = {
soil_moisture: { min: 0, max: 100 },
temperature: { min: -10, max: 60 },
humidity: { min: 0, max: 100 },
ph_level: { min: 0, max: 14 },
nutrient_n: { min: 0, max: 200 },
nutrient_p: { min: 0, max: 200 },
nutrient_k: { min: 0, max: 200 }
};
const range = reasonableRanges[sensor.type];
if (!range) return true; // No range defined for this type
return data.calibrated_value >= range.min && data.calibrated_value <= range.max;
}
checkDataAnomalies(data) {
const sensor = this.sensors.get(data.sensor_id);
if (!sensor || !sensor.lastReading) return;
const lastValue = sensor.lastReading.calibrated_value;
const currentValue = data.calibrated_value;
const change = Math.abs(currentValue - lastValue);
const maxChange = this.getMaxAllowedChange(sensor.type);
if (change > maxChange) {
console.warn(`Anomaly detected in sensor ${data.sensor_id}: Change of ${change} exceeds maximum ${maxChange}`);
this.triggerAnomalyAlert(data, change, maxChange);
}
}
getMaxAllowedChange(sensorType) {
const maxChanges = {
soil_moisture: 10, // 10% change
temperature: 5, // 5°C change
humidity: 15, // 15% change
ph_level: 0.5, // 0.5 pH change
nutrient_n: 20, // 20 ppm change
nutrient_p: 15, // 15 ppm change
nutrient_k: 15 // 15 ppm change
};
return maxChanges[sensorType] || 10;
}
bufferData(data) {
this.dataBuffer.push({
...data,
processed_at: new Date().toISOString()
});
// Maintain buffer size
if (this.dataBuffer.length > this.maxBufferSize) {
this.dataBuffer = this.dataBuffer.slice(-this.maxBufferSize);
}
}
async processBufferedData() {
if (this.dataBuffer.length === 0) return;
try {
const batch = [...this.dataBuffer];
this.dataBuffer = [];
// Group by sensor and time window (1 minute)
const groupedData = this.groupDataBySensorAndTime(batch);
// Calculate aggregates
const aggregates = this.calculateAggregates(groupedData);
// Store in historical data
this.storeHistoricalData(aggregates);
// Update sensor statistics
this.updateSensorStatistics(aggregates);
console.log(`Processed ${batch.length} data points into ${aggregates.length} aggregates`);
} catch (error) {
console.error('Error processing buffered data:', error);
// Put data back in buffer for retry
this.dataBuffer.unshift(...batch);
}
}
groupDataBySensorAndTime(data) {
const grouped = new Map();
data.forEach(item => {
const sensorId = item.sensor_id;
const timeWindow = this.getTimeWindow(item.timestamp, 60000); // 1 minute windows
const key = `${sensorId}-${timeWindow}`;
if (!grouped.has(key)) {
grouped.set(key, []);
}
grouped.get(key).push(item);
});
return grouped;
}
getTimeWindow(timestamp, windowSize) {
const date = new Date(timestamp);
return Math.floor(date.getTime() / windowSize) * windowSize;
}
calculateAggregates(groupedData) {
const aggregates = [];
groupedData.forEach((dataPoints, key) => {
if (dataPoints.length === 0) return;
const [sensorId, timeWindow] = key.split('-');
const sensor = this.sensors.get(sensorId);
if (!sensor) return;
const values = dataPoints.map(d => d.calibrated_value);
const batteries = dataPoints.map(d => d.battery).filter(b => b != null);
const signals = dataPoints.map(d => d.signal_strength).filter(s => s != null);
const aggregate = {
sensor_id: sensorId,
timestamp: new Date(parseInt(timeWindow)).toISOString(),
type: sensor.type,
unit: sensor.unit,
count: values.length,
avg_value: this.calculateAverage(values),
min_value: Math.min(...values),
max_value: Math.max(...values),
std_dev: this.calculateStandardDeviation(values),
avg_battery: batteries.length ? this.calculateAverage(batteries) : null,
avg_signal: signals.length ? this.calculateAverage(signals) : null,
data_points: dataPoints.length
};
aggregates.push(aggregate);
});
return aggregates;
}
calculateAverage(values) {
const sum = values.reduce((a, b) => a + b, 0);
return sum / values.length;
}
calculateStandardDeviation(values) {
const avg = this.calculateAverage(values);
const squareDiffs = values.map(value => Math.pow(value - avg, 2));
const avgSquareDiff = this.calculateAverage(squareDiffs);
return Math.sqrt(avgSquareDiff);
}
storeHistoricalData(aggregates) {
aggregates.forEach(aggregate => {
const sensorData = this.historicalData.get(aggregate.sensor_id) || [];
sensorData.push(aggregate);
this.historicalData.set(aggregate.sensor_id, sensorData);
});
}
// ===== SENSOR MANAGEMENT =====
updateSensorReading(data) {
const sensor = this.sensors.get(data.sensor_id);
if (sensor) {
sensor.lastReading = data;
sensor.lastUpdate = new Date().toISOString();
sensor.battery = data.battery || sensor.battery;
// Update sensor status based on battery and signal
this.updateSensorStatus(sensor, data);
}
}
updateSensorStatus(sensor, data) {
let newStatus = 'online';
if (sensor.battery < 20) {
newStatus = 'low_battery';
} else if (sensor.battery < 10) {
newStatus = 'critical_battery';
}
if (data.signal_strength && data.signal_strength < 50) {
newStatus = 'weak_signal';
}
if (sensor.status !== newStatus) {
const oldStatus = sensor.status;
sensor.status = newStatus;
this.triggerStatusChangeAlert(sensor, oldStatus, newStatus);
}
}
updateSensorStatistics(aggregates) {
aggregates.forEach(aggregate => {
const sensor = this.sensors.get(aggregate.sensor_id);
if (sensor) {
if (!sensor.statistics) {
sensor.statistics = {
totalReadings: 0,
avgValue: 0,
minValue: Infinity,
maxValue: -Infinity,
reliability: 100
};
}
sensor.statistics.totalReadings += aggregate.count;
sensor.statistics.avgValue = aggregate.avg_value;
sensor.statistics.minValue = Math.min(sensor.statistics.minValue, aggregate.min_value);
sensor.statistics.maxValue = Math.max(sensor.statistics.maxValue, aggregate.max_value);
// Calculate reliability based on data quality and uptime
sensor.statistics.reliability = this.calculateReliability(sensor);
}
});
}
calculateReliability(sensor) {
let reliability = 100;
// Deduct for low battery
if (sensor.battery < 30) reliability -= 20;
else if (sensor.battery < 50) reliability -= 10;
// Deduct for poor status
if (sensor.status !== 'online') reliability -= 30;
// Ensure reliability is between 0 and 100
return Math.max(0, Math.min(100, reliability));
}
// ===== ALERT SYSTEM =====
setupAlertSystem() {
this.activeAlerts = new Map();
this.alertCooldown = new Map();
}
checkThresholdAlerts(data) {
const sensor = this.sensors.get(data.sensor_id);
if (!sensor) return;
const threshold = this.alertThresholds[sensor.type];
if (!threshold) return;
const value = data.calibrated_value;
const alertKey = `${data.sensor_id}-threshold`;
// Check if we're in cooldown for this alert
if (this.alertCooldown.has(alertKey)) {
const cooldownUntil = this.alertCooldown.get(alertKey);
if (new Date() < cooldownUntil) return;
}
let alertType = null;
let message = '';
if (value < threshold.min) {
alertType = 'below_min';
message = `${sensor.name} membaca ${value}${sensor.unit}, di bawah minimum ${threshold.min}${sensor.unit}`;
} else if (value > threshold.max) {
alertType = 'above_max';
message = `${sensor.name} membaca ${value}${sensor.unit}, di atas maksimum ${threshold.max}${sensor.unit}`;
}
if (alertType && !this.activeAlerts.has(alertKey)) {
this.triggerThresholdAlert(sensor, alertType, message, value);
// Set cooldown for 5 minutes
this.alertCooldown.set(alertKey, new Date(Date.now() + 300000));
} else if (!alertType && this.activeAlerts.has(alertKey)) {
// Clear alert if back to normal
this.clearAlert(alertKey);
}
}
triggerThresholdAlert(sensor, type, message, value) {
const alert = {
id: `threshold-${sensor.id}-${Date.now()}`,
type: 'threshold',
sensor: sensor,
severity: this.getAlertSeverity(type, value),
message: message,
timestamp: new Date().toISOString(),
value: value,
resolved: false
};
this.activeAlerts.set(alert.id, alert);
this.broadcastAlert(alert);
console.log(`Threshold alert: ${message}`);
}
triggerAnomalyAlert(data, change, maxChange) {
const sensor = this.sensors.get(data.sensor_id);
if (!sensor) return;
const alert = {
id: `anomaly-${sensor.id}-${Date.now()}`,
type: 'anomaly',
sensor: sensor,
severity: 'high',
message: `Anomali terdeteksi di ${sensor.name}: perubahan ${change.toFixed(2)}${sensor.unit} melebihi batas ${maxChange}${sensor.unit}`,
timestamp: new Date().toISOString(),
change: change,
maxChange: maxChange,
resolved: false
};
this.activeAlerts.set(alert.id, alert);
this.broadcastAlert(alert);
console.log(`Anomaly alert: ${alert.message}`);
}
triggerStatusChangeAlert(sensor, oldStatus, newStatus) {
const alert = {
id: `status-${sensor.id}-${Date.now()}`,
type: 'status_change',
sensor: sensor,
severity: this.getStatusSeverity(newStatus),
message: `Status ${sensor.name} berubah dari ${oldStatus} menjadi ${newStatus}`,
timestamp: new Date().toISOString(),
oldStatus: oldStatus,
newStatus: newStatus,
resolved: false
};
this.activeAlerts.set(alert.id, alert);
this.broadcastAlert(alert);
console.log(`Status change alert: ${alert.message}`);
}
getAlertSeverity(type, value) {
if (type === 'below_min') {
const threshold = this.alertThresholds[type.split('_')[1]];
const deviation = ((threshold.min - value) / threshold.min) * 100;
return deviation > 30 ? 'critical' : deviation > 15 ? 'high' : 'medium';
} else {
const threshold = this.alertThresholds[type.split('_')[1]];
const deviation = ((value - threshold.max) / threshold.max) * 100;
return deviation > 30 ? 'critical' : deviation > 15 ? 'high' : 'medium';
}
}
getStatusSeverity(status) {
const severityMap = {
'online': 'low',
'low_battery': 'medium',
'weak_signal': 'medium',
'critical_battery': 'high',
'offline': 'high'
};
return severityMap[status] || 'medium';
}
clearAlert(alertId) {
const alert = this.activeAlerts.get(alertId);
if (alert) {
alert.resolved = true;
alert.resolved_at = new Date().toISOString();
this.broadcastAlertResolution(alert);
this.activeAlerts.delete(alertId);
}
}
// ===== DATA RETRIEVAL =====
async getSensorReadings(sensorId, options = {}) {
const {
startTime = new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
endTime = new Date(),
aggregate = '1h' // 1 hour aggregates
} = options;
const rawData = this.historicalData.get(sensorId) || [];
// Filter by time range
let filteredData = rawData.filter(reading => {
const readingTime = new Date(reading.timestamp);
return readingTime >= startTime && readingTime <= endTime;
});
// Apply additional aggregation if needed
if (aggregate !== 'raw') {
filteredData = this.aggregateData(filteredData, aggregate);
}
return filteredData.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
}
aggregateData(data, interval) {
const intervalMs = this.parseAggregateInterval(interval);
const aggregated = new Map();
data.forEach(reading => {
const timeWindow = this.getTimeWindow(reading.timestamp, intervalMs);
const key = timeWindow.toString();
if (!aggregated.has(key)) {
aggregated.set(key, {
timestamp: new Date(timeWindow).toISOString(),
values: [],
batteries: [],
signals: []
});
}
const aggregate = aggregated.get(key);
aggregate.values.push(reading.avg_value);
if (reading.avg_battery) aggregate.batteries.push(reading.avg_battery);
if (reading.avg_signal) aggregate.signals.push(reading.avg_signal);
});
// Calculate final aggregates
const result = [];
aggregated.forEach((agg, key) => {
result.push({
timestamp: agg.timestamp,
avg_value: this.calculateAverage(agg.values),
min_value: Math.min(...agg.values),
max_value: Math.max(...agg.values),
avg_battery: agg.batteries.length ? this.calculateAverage(agg.batteries) : null,
avg_signal: agg.signals.length ? this.calculateAverage(agg.signals) : null,
sample_count: agg.values.length
});
});
return result.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
}
parseAggregateInterval(interval) {
const units = {
'm': 60 * 1000, // minutes
'h': 60 * 60 * 1000, // hours
'd': 24 * 60 * 60 * 1000 // days
};
const value = parseInt(interval);
const unit = interval.replace(value.toString(), '');
return value * (units[unit] || 60 * 60 * 1000); // Default to 1 hour
}
getAllSensors() {
return Array.from(this.sensors.values());
}
getSensor(sensorId) {
return this.sensors.get(sensorId);
}
getActiveAlerts() {
return Array.from(this.activeAlerts.values()).filter(alert => !alert.resolved);
}
// ===== DATA BROADCASTING =====
broadcastDataUpdate(data) {
// Broadcast to all connected dashboard components
if (window.dashboard && typeof window.dashboard.handleSensorData === 'function') {
window.dashboard.handleSensorData(data);
}
// Dispatch custom event for other components to listen to
const event = new CustomEvent('sensorDataUpdate', { detail: data });
window.dispatchEvent(event);
}
broadcastAlert(alert) {
if (window.dashboard && typeof window.dashboard.handleSensorAlert === 'function') {
window.dashboard.handleSensorAlert(alert);
}
const event = new CustomEvent('sensorAlert', { detail: alert });
window.dispatchEvent(event);
}
broadcastAlertResolution(alert) {
const event = new CustomEvent('sensorAlertResolved', { detail: alert });
window.dispatchEvent(event);
}
broadcastSensorStatus(status) {
const event = new CustomEvent('sensorConnectionStatus', { detail: status });
window.dispatchEvent(event);
}
// ===== DATA MAINTENANCE =====
cleanHistoricalData() {
const cutoffTime = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
this.historicalData.forEach((readings, sensorId) => {
const filtered = readings.filter(reading =>
new Date(reading.timestamp) >= cutoffTime
);
this.historicalData.set(sensorId, filtered);
});
console.log('Historical data cleaned');
}
loadHistoricalData() {
// In a real implementation, this would load from database
// For now, we'll generate some historical data
this.generateHistoricalData(7); // Generate 7 days of historical data
}
generateHistoricalData(days) {
const now = new Date();
const startTime = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
this.sensors.forEach((sensor, sensorId) => {
const historical = [];
let currentTime = new Date(startTime);
while (currentTime < now) {
const reading = this.generateHistoricalReading(sensor.type, currentTime);
historical.push({
sensor_id: sensorId,
timestamp: currentTime.toISOString(),
type: sensor.type,
unit: sensor.unit,
avg_value: reading.value,
min_value: reading.value - Math.random() * 2,
max_value: reading.value + Math.random() * 2,
std_dev: Math.random() * 0.5,
data_points: 60 // Assuming 1 reading per minute
});
currentTime = new Date(currentTime.getTime() + 60 * 60 * 1000); // Add 1 hour
}
this.historicalData.set(sensorId, historical);
});
console.log(`Generated ${days} days of historical data for ${this.sensors.size} sensors`);
}
// ===== SIMULATION HELPERS =====
generateSensorReading(type, sensorId) {
const baseValues = {
soil_moisture: () => this.randomInt(30, 80),
temperature: () => 25 + Math.sin(Date.now() / 3600000) * 5 + (Math.random() * 2 - 1),
humidity: () => 60 + Math.cos(Date.now() / 3600000) * 20 + (Math.random() * 5 - 2.5),
ph_level: () => 6.0 + Math.random() * 1.5,
nutrient_n: () => this.randomInt(20, 60),
nutrient_p: () => this.randomInt(15, 40),
nutrient_k: () => this.randomInt(10, 50)
};
const generator = baseValues[type] || (() => this.randomInt(0, 100));
return { value: generator() };
}
generateHistoricalReading(type, timestamp) {
const hour = new Date(timestamp).getHours();
// Simulate daily patterns
const patterns = {
soil_moisture: () => {
const base = 50;
const dailyPattern = Math.sin((hour - 6) * Math.PI / 12) * 10; // Drier during day
return base + dailyPattern + (Math.random() * 10 - 5);
},
temperature: () => {
const base = 25;
const dailyPattern = Math.sin((hour - 14) * Math.PI / 12) * 8; // Warmer during day
return base + dailyPattern + (Math.random() * 3 - 1.5);
},
humidity: () => {
const base = 60;
const dailyPattern = Math.cos((hour - 6) * Math.PI / 12) * 15; // More humid at night
return base + dailyPattern + (Math.random() * 8 - 4);
},
ph_level: () => 6.0 + Math.random() * 1.5,
nutrient_n: () => this.randomInt(20, 60),
nutrient_p: () => this.randomInt(15, 40),
nutrient_k: () => this.randomInt(10, 50)
};
const generator = patterns[type] || (() => this.randomInt(0, 100));
return { value: generator() };
}
simulateSensorStatusChange() {
// 2% chance for any sensor to change status
this.sensors.forEach(sensor => {
if (Math.random() < 0.02) {
const oldStatus = sensor.status;
if (sensor.status === 'online') {
// 70% chance for low battery, 30% for offline
sensor.status = Math.random() < 0.7 ? 'low_battery' : 'offline';
} else {
// Return to online
sensor.status = 'online';
}
if (oldStatus !== sensor.status) {
this.triggerStatusChangeAlert(sensor, oldStatus, sensor.status);
}
}
});
}
randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// ===== PUBLIC API =====
startDataCollection() {
this.isCollecting = true;
console.log('Sensor data collection started');
}
stopDataCollection() {
this.isCollecting = false;
console.log('Sensor data collection stopped');
}
setSamplingRate(rate) {
this.samplingRate = rate;
console.log(`Sampling rate set to ${rate}ms`);
}
getDataSummary() {
const summary = {
totalSensors: this.sensors.size,
onlineSensors: Array.from(this.sensors.values()).filter(s => s.status === 'online').length,
totalReadings: Array.from(this.historicalData.values()).reduce((sum, readings) => sum + readings.length, 0),
activeAlerts: this.getActiveAlerts().length,
dataBufferSize: this.dataBuffer.length,
memoryUsage: this.getMemoryUsage()
};
return summary;
}
getMemoryUsage() {
// Rough estimate of memory usage
const dataSize = JSON.stringify(this.dataBuffer).length +
JSON.stringify(Array.from(this.historicalData.values())).length;
return Math.round(dataSize / 1024); // KB
}
// ===== DESTRUCTOR =====
destroy() {
this.stopDataCollection();
this.activeAlerts.clear();
this.alertCooldown.clear();
this.dataBuffer = [];
console.log('Sensor Data Manager destroyed');
}
}
// ===== GLOBAL SENSOR MANAGER INSTANCE =====
let sensorManager;
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeSensorManager);
} else {
initializeSensorManager();
}
function initializeSensorManager() {
sensorManager = new SensorDataManager();
window.sensorManager = sensorManager;
console.log('Sensor Data Manager global instance created');
}
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = { SensorDataManager, sensorManager };
}
```
Cara Penggunaan:
1. Simpan file sebagai sensor-data.js di folder JS project Anda
2. Hubungkan ke HTML dengan menambahkan sebelum penutup </body> setelah dashboard.js:
```html
<script src="js/main.js"></script>
<script src="js/dashboard.js"></script>
<script src="js/sensor-data.js"></script>
```
Fitur Utama Sensor Data.js:
π§ Sensor Management
· Sensor Registry: Pendaftaran dan metadata sensor IoT
· Status Monitoring: Real-time status tracking (online, offline, low battery)
· Calibration System: Kalibrasi data sensor dengan offset dan faktor
· Battery Management: Monitoring level baterai sensor
π Data Processing Pipeline
· Real-time Processing: Pemrosesan data sensor secara real-time
· Data Validation: Validasi kualitas data dan range yang reasonable
· Anomaly Detection: Deteksi perubahan data yang tidak normal
· Data Aggregation: Aggregasi data per menit/jam/hari
π¨ Advanced Alert System
· Threshold Alerts: Alert berdasarkan batas minimum/maksimum
· Anomaly Alerts: Alert untuk perubahan data yang mencurigakan
· Status Alerts: Alert untuk perubahan status sensor
· Smart Severity: Sistem severity otomatis berdasarkan deviasi
πΎ Data Management
· Buffering System: Buffer data untuk processing batch
· Historical Storage: Penyimpanan data historis dengan aggregasi
· Data Cleaning: Auto-clean data lama (30 hari)
· Memory Management: Monitoring penggunaan memory
π Real-time Communication
· WebSocket Simulation: Simulasi koneksi WebSocket untuk data real-time
· Event Broadcasting: System events untuk komponen lain
· Data Broadcasting: Broadcast data update ke dashboard
π Analytics & Reporting
· Statistical Analysis: Rata-rata, min, max, standard deviation
· Reliability Scoring: Scoring keandalan sensor berdasarkan berbagai faktor
· Data Summaries: Summary data untuk reporting
· Trend Analysis: Analisis pola data harian
π ️ Maintenance Features
· Auto-recovery: System recovery untuk koneksi terputus
· Data Integrity: Validasi dan repair data corrupt
· Performance Monitoring: Monitoring performa system
· Resource Management: Efficient memory dan CPU usage
π Integration Ready
· Modular Architecture: Mudah diintegrasikan dengan sistem lain
· Event-driven: System based on custom events
· API Ready: Methods untuk data retrieval dan management
· Extensible: Mudah ditambah sensor types dan processing rules
File ini memberikan foundation yang robust untuk manajemen data sensor IoT dengan fitur-fitur enterprise-grade untuk monitoring dan analisis data pertanian presisi.
π‘πΎπππΊππ»πππ ππΎ: πͺππ½πΎ π¨ππ π₯ππ πΎ ai-recommendations.js
Comments
Post a Comment