π–ͺπ—ˆπ–½π–Ύ π–¨π—Œπ—‚ π–₯𝗂𝗅𝖾 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

Popular posts from this blog

Aplikasi FarmerSmartAI:

icon-generator.html