1. Latar Belakang & Motivasi

Permasalahan

Kualitas observasi astronomi sangat bergantung pada kondisi atmosfer yang tidak bisa dikontrol. Astronomer amatir sering menghadapi masalah: - Mempersiapkan peralatan hanya untuk menemukan kondisi langit buruk - Tidak ada sistem peringatan dini untuk perubahan cuaca mendadak - Kesulitan memprediksi kapan kondisi "seeing" optimal untuk observasi/astrophotography

Atmospheric Seeing

Seeing adalah ukuran degradasi citra astronomi akibat turbulensi atmosfer, diukur dalam satuan arcsecond ("). Seeing yang baik (<2") memungkinkan detail lebih tajam, sedangkan seeing buruk (>4") membuat citra blur meski teleskop berkualitas tinggi.

Faktor yang mempengaruhi seeing: - Gradien suhu antara permukaan dan atmosfer - Kecepatan dan arah angin - Kelembaban udara - Tekanan barometrik - Keberadaan awan

Gap Penelitian

Mayoritas penelitian prediksi seeing dilakukan di observatorium profesional dengan peralatan mahal (DIMM, MASS, dll). Belum ada solusi low-cost untuk astronomer amatir yang mengintegrasikan IoT dan machine learning untuk prediksi kondisi observasi.


2. Rumusan Masalah

  1. Bagaimana merancang sistem IoT low-cost untuk monitoring kondisi langit yang relevan dengan observasi astronomi?
  2. Bagaimana mengembangkan model machine learning untuk klasifikasi kondisi langit (clear, cloudy, moonlit)?
  3. Bagaimana membangun model prediksi kualitas seeing berdasarkan data meteorologi lokal?
  4. Seberapa akurat sistem yang dikembangkan dibandingkan dengan pengamatan visual?

3. Tujuan Penelitian

Tujuan Umum

Mengembangkan Smart Observatory Weather Station berbasis IoT dengan kemampuan prediksi kondisi observasi menggunakan machine learning.

Tujuan Khusus

  1. Membangun hardware weather station dengan sensor-sensor relevan untuk astronomi
  2. Mengembangkan backend system untuk data collection dan storage
  3. Melatih model ML untuk klasifikasi kondisi langit
  4. Membangun model prediksi seeing quality index
  5. Mengembangkan dashboard dan sistem notifikasi

4. Arsitektur Sistem

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     SENSOR NODE (Outdoor)                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚ BME280   β”‚  β”‚MLX90614  β”‚  β”‚ TSL2591  β”‚  β”‚Anemometerβ”‚        β”‚
β”‚  β”‚Temp/Hum/ β”‚  β”‚Cloud/Sky β”‚  β”‚Light/Lux β”‚  β”‚Wind Speedβ”‚        β”‚
β”‚  β”‚Pressure  β”‚  β”‚Temp (IR) β”‚  β”‚Meter     β”‚  β”‚Direction β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜        β”‚
β”‚       β”‚             β”‚             β”‚             β”‚               β”‚
β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                            β”‚                                    β”‚
β”‚                     β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”                             β”‚
β”‚                     β”‚   ESP32     β”‚                             β”‚
β”‚                     β”‚  + WiFi     β”‚                             β”‚
β”‚                     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚ MQTT/HTTP
                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    EDGE SERVER (Raspberry Pi)                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚
β”‚  β”‚ MQTT      β”‚  β”‚ PostgreSQL β”‚  β”‚ Django     β”‚                β”‚
β”‚  β”‚ Broker    β”‚  β”‚ TimescaleDBβ”‚  β”‚ REST API   β”‚                β”‚
β”‚  β”‚(Mosquitto)β”‚  β”‚            β”‚  β”‚            β”‚                β”‚
β”‚  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                β”‚
β”‚        β”‚               β”‚               β”‚                        β”‚
β”‚        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β”‚                                β”‚                                β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”‚
β”‚                    β”‚   ML Pipeline         β”‚                    β”‚
β”‚                    β”‚  - Feature Engineeringβ”‚                    β”‚
β”‚                    β”‚  - Model Inference    β”‚                    β”‚
β”‚                    β”‚  - Prediction Service β”‚                    β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        CLIENT LAYER                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚
β”‚  β”‚ Web       β”‚  β”‚ Telegram   β”‚  β”‚ INDI       β”‚                β”‚
β”‚  β”‚ Dashboard β”‚  β”‚ Bot Alert  β”‚  β”‚ Driver     β”‚                β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

5. Komponen Hardware

5.1 Sensor Suite

Sensor Fungsi Interface Est. Harga
BME280 Suhu, kelembaban, tekanan atmosfer I2C Rp 35.000
MLX90614-BCF Sky temperature (cloud detection) I2C Rp 85.000
TSL2591 Sky brightness (lux & IR) I2C Rp 65.000
Wind Sensor Kit Kecepatan & arah angin Analog Rp 150.000
Rain Sensor Deteksi hujan Digital Rp 15.000

5.2 Controller & Communication

Komponen Fungsi Est. Harga
ESP32 DevKit Microcontroller + WiFi Rp 65.000
Raspberry Pi 4 (2GB) Edge server Rp 750.000
MCP3008 ADC untuk sensor analog Rp 25.000

5.3 Cloud Detection dengan MLX90614

Prinsip kerja: - Sensor IR mengukur suhu langit (sky temperature) - Langit cerah: suhu sangat rendah (-20Β°C sampai -40Β°C) - Berawan: suhu lebih tinggi (mendekati ambient)

Formula Cloud Index:

Sky_Delta = Ambient_Temp - Sky_Temp

Kondisi:
- Sky_Delta ≀ 5Β°C  β†’ Heavily Cloudy (100%)
- Sky_Delta 5-11Β°C β†’ Mostly Cloudy (75%)  
- Sky_Delta 11-16Β°C β†’ Partly Cloudy (50%)
- Sky_Delta 16-19Β°C β†’ Mostly Clear (25%)
- Sky_Delta > 19Β°C β†’ Clear Sky (0%)

5.4 Sky Quality Measurement dengan TSL2591

  • Mengukur luminositas dalam lux dan magnitude/arcsecΒ²
  • Konversi ke Bortle Scale (1-9) untuk klasifikasi light pollution
  • Deteksi moonlight untuk klasifikasi "moonlit nights"

6. Metodologi Machine Learning

6.1 Dataset & Features

Data Collection Period: Minimum 60 hari (2 bulan) Sampling Rate: Setiap 1 menit

Feature Set:

Feature Sumber Tipe
ambient_temp BME280 Continuous
humidity BME280 Continuous
pressure BME280 Continuous
sky_temp MLX90614 Continuous
sky_delta Calculated Continuous
sky_brightness TSL2591 Continuous
wind_speed Anemometer Continuous
wind_direction Wind vane Categorical
pressure_trend_1h Calculated Continuous
temp_gradient_1h Calculated Continuous
hour_of_day System Cyclical
moon_phase Ephemeris API Continuous
moon_altitude Ephemeris API Continuous

6.2 Target Variables

Task 1: Sky Condition Classification - Class 0: Clear - Class 1: Partly Cloudy - Class 2: Cloudy/Overcast - Class 3: Clear Moonlit - Class 4: Cloudy Moonlit - Class 5: Rain/Unusable

Task 2: Seeing Quality Index Regression - Output: Seeing Quality Score (0-100) - 0-30: Poor (tidak disarankan observasi) - 30-50: Fair (visual observation OK) - 50-70: Good (imaging possible) - 70-100: Excellent (high-res imaging)

6.3 Model Selection

Untuk Klasifikasi (Sky Condition):

# Recommended: Random Forest
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(
    n_estimators=100,
    max_depth=15,
    min_samples_split=5,
    class_weight='balanced'
)

Alasan: - Robust terhadap outliers - Handles imbalanced classes - Feature importance built-in - Sudah terbukti efektif untuk klasifikasi data SQM

Untuk Regresi (Seeing Index):

# Recommended: Gradient Boosting
from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor(
    n_estimators=200,
    max_depth=5,
    learning_rate=0.1,
    loss='huber'
)

6.4 Training Pipeline

# Pseudo-code pipeline
class SeeingPredictionPipeline:

    def preprocess(self, df):
        # Handle missing values
        # Encode cyclical features (hour, wind_direction)
        # Calculate derived features
        # Normalize continuous features
        return processed_df

    def extract_features(self, df):
        # Statistical features over rolling windows
        # 15-min, 30-min, 1-hour aggregations
        features = {
            'temp_mean_15m': df['ambient_temp'].rolling(15).mean(),
            'pressure_std_30m': df['pressure'].rolling(30).std(),
            'sky_delta_trend': df['sky_delta'].diff(30),
            # ... more features
        }
        return features

    def train(self, X, y):
        # Cross-validation with TimeSeriesSplit
        # Hyperparameter tuning
        # Model selection
        pass

    def predict(self, current_data):
        # Real-time inference
        # Return prediction + confidence
        pass

6.5 Evaluation Metrics

Klasifikasi: - Accuracy, Precision, Recall, F1-Score - Confusion Matrix - Focus on: Recall untuk class "Rain" (safety-critical)

Regresi: - RMSE (Root Mean Square Error) - MAE (Mean Absolute Error) - RΒ² Score - Target: RMSE < 15 untuk seeing index


7. Implementasi Software

7.1 ESP32 Firmware (MicroPython)

# sensor_node.py
import machine
import network
from umqtt.simple import MQTTClient
import json
import time

class WeatherStation:
    def __init__(self):
        self.i2c = machine.I2C(0, scl=22, sda=21)
        self.bme280 = BME280(i2c=self.i2c)
        self.mlx90614 = MLX90614(i2c=self.i2c)
        self.tsl2591 = TSL2591(i2c=self.i2c)

    def read_sensors(self):
        return {
            'timestamp': time.time(),
            'ambient_temp': self.bme280.temperature,
            'humidity': self.bme280.humidity,
            'pressure': self.bme280.pressure,
            'sky_temp': self.mlx90614.object_temp,
            'sensor_temp': self.mlx90614.ambient_temp,
            'lux': self.tsl2591.lux,
            'ir': self.tsl2591.infrared
        }

    def publish(self, client, data):
        topic = 'observatory/weather'
        client.publish(topic, json.dumps(data))

7.2 Django Backend

# models.py
from django.db import models
from timescale.db.models.models import TimescaleModel

class WeatherReading(TimescaleModel):
    timestamp = models.DateTimeField(primary_key=True)
    ambient_temp = models.FloatField()
    humidity = models.FloatField()
    pressure = models.FloatField()
    sky_temp = models.FloatField()
    sky_brightness = models.FloatField()
    wind_speed = models.FloatField(null=True)
    wind_direction = models.IntegerField(null=True)

    class Meta:
        ordering = ['-timestamp']

class SkyConditionPrediction(TimescaleModel):
    timestamp = models.DateTimeField(primary_key=True)
    predicted_class = models.IntegerField()
    confidence = models.FloatField()
    seeing_index = models.FloatField()

# views.py (API)
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

class WeatherViewSet(viewsets.ModelViewSet):
    queryset = WeatherReading.objects.all()

    @action(detail=False, methods=['get'])
    def current_conditions(self, request):
        latest = self.queryset.first()
        prediction = self.ml_service.predict(latest)
        return Response({
            'weather': WeatherSerializer(latest).data,
            'prediction': prediction,
            'recommendation': self.get_recommendation(prediction)
        })

    def get_recommendation(self, prediction):
        if prediction['seeing_index'] > 70:
            return "Excellent conditions! Great for astrophotography."
        elif prediction['seeing_index'] > 50:
            return "Good conditions for visual observation."
        else:
            return "Consider waiting for better conditions."

7.3 ML Service

# ml_service.py
import joblib
import numpy as np
from datetime import datetime

class MLPredictionService:
    def __init__(self):
        self.classifier = joblib.load('models/sky_classifier.pkl')
        self.regressor = joblib.load('models/seeing_regressor.pkl')
        self.scaler = joblib.load('models/feature_scaler.pkl')

    def prepare_features(self, reading, historical_data):
        """Extract features from current and historical readings"""
        features = {
            'ambient_temp': reading.ambient_temp,
            'humidity': reading.humidity,
            'pressure': reading.pressure,
            'sky_delta': reading.ambient_temp - reading.sky_temp,
            'sky_brightness': reading.sky_brightness,
            # Add rolling statistics from historical_data
            'temp_trend_1h': self.calculate_trend(historical_data, 'ambient_temp'),
            'pressure_trend_1h': self.calculate_trend(historical_data, 'pressure'),
        }
        return np.array(list(features.values())).reshape(1, -1)

    def predict(self, reading, historical_data=None):
        features = self.prepare_features(reading, historical_data)
        scaled_features = self.scaler.transform(features)

        sky_class = self.classifier.predict(scaled_features)[0]
        sky_proba = self.classifier.predict_proba(scaled_features)[0]
        seeing_index = self.regressor.predict(scaled_features)[0]

        return {
            'sky_condition': int(sky_class),
            'sky_condition_label': self.CLASS_LABELS[sky_class],
            'confidence': float(max(sky_proba)),
            'seeing_index': float(seeing_index),
            'seeing_quality': self.get_quality_label(seeing_index)
        }

8. Ground Truth & Validation

8.1 Labeling Strategy

Karena tidak memiliki DIMM profesional, gunakan proxy measures:

  1. Visual Observation Log
  2. Catat kondisi langit setiap malam observasi
  3. Rating subjektif 1-5 untuk seeing
  4. Foto referensi dengan smartphone

  5. Astrophotography Quality

  6. Analisis FWHM dari star images
  7. Bandingkan sharpness antar malam

  8. Star Trail Analysis

  9. Rekam star trail pendek (30 detik)
  10. Analisis "wobble" sebagai proxy seeing

8.2 Validation Methods

# Cross-validation dengan TimeSeriesSplit
from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
scores = []

for train_idx, test_idx in tscv.split(X):
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]

    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)
    scores.append(score)

9. Timeline Pengerjaan

Minggu Aktivitas
1-2 Perancangan sistem & pengadaan komponen
3-4 Assembly hardware & testing sensor
5-6 Development firmware ESP32
7-8 Setup Raspberry Pi & Django backend
9-12 Data Collection Phase
13-14 Data preprocessing & feature engineering
15-16 Model training & hyperparameter tuning
17-18 Integration testing & dashboard development
19-20 Validation & penulisan laporan

10. Estimasi Biaya

Kategori Item Harga
Sensor BME280, MLX90614, TSL2591, Wind kit, Rain Rp 350.000
Controller ESP32 DevKit Rp 65.000
Server Raspberry Pi 4 (2GB) + SD Card Rp 850.000
Enclosure Weatherproof box, mounting Rp 150.000
Power Power supply, cables Rp 100.000
Miscellaneous PCB, connectors, tools Rp 200.000
Total Rp 1.715.000

Note: Jika sudah punya Raspberry Pi, budget bisa dikurangi signifikan.


11. Referensi Utama

  1. Priyatikanto et al. (2020). "Classification of Continuous Sky Brightness Data Using Random Forest." Advances in Astronomy. [Indonesia-based research]

  2. Hou et al. (2023). "Machine learning-based seeing estimation and prediction using multi-layer meteorological data." arXiv:2304.03587

  3. MDPI (2025). "Machine-Learning-Based Monitoring of Night Sky Brightness Using Sky Quality Meters." Remote Sensing

  4. WeatherRadio Project - INDI Library (Open source astronomical weather station)


12. Potential Extensions

  1. All-Sky Camera Integration - Tambah fisheye camera untuk visual confirmation
  2. Satellite Data Fusion - Integrasikan data BMKG atau weather API
  3. Multi-node Network - Deploy di beberapa lokasi untuk coverage lebih luas
  4. Mobile App - Notifikasi push ke smartphone
  5. INDI Driver - Integrasi dengan software astronomi seperti KStars/Ekos