exec(" CREATE TABLE IF NOT EXISTS monitoring_components ( id INT AUTO_INCREMENT PRIMARY KEY, component_key VARCHAR(50) UNIQUE NOT NULL, component_name VARCHAR(100) NOT NULL, component_type VARCHAR(50) NOT NULL DEFAULT 'sensor', status ENUM('online','offline','warning','error') DEFAULT 'offline', value VARCHAR(100) DEFAULT NULL, unit VARCHAR(20) DEFAULT NULL, icon VARCHAR(50) DEFAULT 'mdi-chip', color VARCHAR(20) DEFAULT '#4B49AC', image VARCHAR(100) DEFAULT NULL, extra_data JSON DEFAULT NULL, last_seen DATETIME DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 "); $pdo->exec(" CREATE TABLE IF NOT EXISTS monitoring_history ( id INT AUTO_INCREMENT PRIMARY KEY, component_id INT NOT NULL, value VARCHAR(100) DEFAULT NULL, status ENUM('online','offline','warning','error') DEFAULT 'online', recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (component_id) REFERENCES monitoring_components(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 "); // ─── Ensure columns exist ─────────────────────────────────────────────────── try { $pdo->query("SELECT extra_data FROM monitoring_components LIMIT 1"); } catch (Exception $e) { $pdo->exec("ALTER TABLE monitoring_components ADD COLUMN extra_data JSON DEFAULT NULL"); } try { $pdo->query("SELECT image FROM monitoring_components LIMIT 1"); } catch (Exception $e) { $pdo->exec("ALTER TABLE monitoring_components ADD COLUMN image VARCHAR(100) DEFAULT NULL"); } // ─── Seed defaults ────────────────────────────────────────────────────────── $count = $pdo->query("SELECT COUNT(*) FROM monitoring_components")->fetchColumn(); if ($count == 0) { $defaults = [ ['esp32', 'Mikrokontroler ESP32', 'controller', 'offline', null, null, 'mdi-developer-board', '#4B49AC', 'esp32.jpeg', json_encode(['wifi_status'=>'off','wifi_rssi'=>'0','wifi_ssid'=>'','wifi_ip'=>'', 'bluetooth'=>'off','cpu_freq'=>'240','free_heap'=>'0', 'uptime'=>'0','temperature'=>'0','voltage_in'=>'0'])], ['fingerprint', 'Sensor Sidik Jari AS608', 'sensor', 'offline', null, null, 'mdi-fingerprint', '#F3797E', 'as608.jpeg', json_encode(['sensor_state'=>'ready','enrolled_count'=>'0', 'last_scan_result'=>'none','confidence'=>'0','last_matched_id'=>''])], ['tft_screen', 'Layar TFT Touch Screen ILI9488', 'display', 'offline', null, null, 'mdi-monitor', '#7DA0FA', 'ili9488.jpg', json_encode(['display_state'=>'active','current_page'=>'Halaman Standby', 'brightness'=>'100','touch_enabled'=>'yes','resolution'=>'480x320'])], ['relay', 'Modul Relay 5V 1 Channel', 'actuator', 'offline', null, null, 'mdi-electric-switch', '#7978E9', 'relay.png', json_encode(['relay_state'=>'off','circuit_connected'=>'no','trigger_count'=>'0'])], ['solenoid_lock', 'Solenoid Door Lock NC 12V', 'actuator', 'offline', null, null, 'mdi-lock', '#E74C3C', 'solenoid.jpg', json_encode(['lock_state'=>'locked','voltage'=>'0','power_watts'=>'0', 'open_count'=>'0','last_opened'=>''])], ['buzzer', 'Buzzer', 'actuator', 'offline', null, null, 'mdi-volume-high', '#4ECDC4', 'buzzer.jpeg', json_encode(['buzzer_state'=>'silent','last_tone'=>'none','beep_count'=>'0'])], ['led_red', 'LED Indikator Merah', 'indicator', 'offline', null, null, 'mdi-led-on', '#FF6B6B', 'led_red.png', json_encode(['led_state'=>'off','blink_mode'=>'none','brightness'=>'0'])], ['led_green', 'LED Indikator Hijau', 'indicator', 'offline', null, null, 'mdi-led-on', '#51CF66', 'led_green.png', json_encode(['led_state'=>'off','blink_mode'=>'none','brightness'=>'0'])], ['power_supply', 'Adaptor Power Supply 12V', 'power', 'offline', '0', 'V', 'mdi-power-plug', '#FFA726', 'power_supply.png', json_encode(['voltage_in'=>'0','voltage_out'=>'0','current_out'=>'0', 'power_watts'=>'0'])], ['step_down', 'Step Down 12V ke 5V LM2596', 'power', 'offline', '0', 'V', 'mdi-arrow-down-bold', '#7DA0FA', 'step_down.png', json_encode(['state'=>'active','voltage_in'=>'0','voltage_out'=>'0', 'current_out'=>'0','power_watts'=>'0','temperature'=>'0','efficiency'=>'0'])], ['battery', 'Baterai Li-ion 18650', 'power', 'offline', '0', '%', 'mdi-battery', '#66BB6A', 'battery.png', json_encode(['bat_state'=>'standby','voltage'=>'0','charge_percent'=>'0', 'current'=>'0','power_watts'=>'0','temperature'=>'0'])], ]; $stmt = $pdo->prepare("INSERT INTO monitoring_components (component_key,component_name,component_type,status,value,unit,icon,color,image,extra_data) VALUES (?,?,?,?,?,?,?,?,?,?)"); foreach ($defaults as $d) $stmt->execute($d); } // ─── Router ───────────────────────────────────────────────────────────────── $action = $_GET['action'] ?? ''; switch ($action) { case 'status': $rows = $pdo->query(" SELECT *, TIMESTAMPDIFF(SECOND, last_seen, NOW()) as seconds_ago FROM monitoring_components ORDER BY FIELD(component_type,'controller','sensor','display','actuator','indicator','power'), id ")->fetchAll(); foreach ($rows as &$r) { if ($r['last_seen'] && $r['seconds_ago'] > 30 && $r['status'] === 'online') { $pdo->prepare("UPDATE monitoring_components SET status='offline' WHERE id=?")->execute([$r['id']]); $r['status'] = 'offline'; } if (isset($r['extra_data']) && is_string($r['extra_data'])) $r['extra_data'] = json_decode($r['extra_data'], true); } $online = count(array_filter($rows, fn($r) => $r['status'] === 'online')); $warning = count(array_filter($rows, fn($r) => $r['status'] === 'warning')); $error = count(array_filter($rows, fn($r) => $r['status'] === 'error')); $offline = count(array_filter($rows, fn($r) => $r['status'] === 'offline')); echo json_encode([ 'success' => true, 'data' => $rows, 'summary' => ['total'=>count($rows),'online'=>$online,'warning'=>$warning,'error'=>$error,'offline'=>$offline] ]); break; case 'update': $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; $key = $input['key'] ?? ''; $status = $input['status'] ?? 'online'; $value = $input['value'] ?? null; $extra = $input['extra'] ?? null; if (!$key) { echo json_encode(['success'=>false,'error'=>'Missing key']); break; } $stmt = $pdo->prepare("SELECT * FROM monitoring_components WHERE component_key=?"); $stmt->execute([$key]); $comp = $stmt->fetch(); if (!$comp) { echo json_encode(['success'=>false,'error'=>'Not found: '.$key]); break; } $cur = json_decode($comp['extra_data'] ?? '{}', true) ?: []; if ($extra && is_array($extra)) $cur = array_merge($cur, $extra); $pdo->prepare("UPDATE monitoring_components SET status=?, value=?, extra_data=?, last_seen=NOW() WHERE component_key=?") ->execute([$status, $value, json_encode($cur), $key]); $pdo->prepare("INSERT INTO monitoring_history (component_id, value, status) VALUES (?,?,?)") ->execute([$comp['id'], $value, $status]); echo json_encode(['success'=>true,'message'=>'Updated '.$key]); break; case 'history': $id = (int)($_GET['id'] ?? 0); $limit = (int)($_GET['limit'] ?? 50); $stmt = $pdo->prepare("SELECT h.*, c.component_name, c.unit FROM monitoring_history h JOIN monitoring_components c ON c.id=h.component_id WHERE h.component_id=? ORDER BY h.recorded_at DESC LIMIT ?"); $stmt->execute([$id, $limit]); echo json_encode(['success'=>true,'data'=>$stmt->fetchAll()]); break; case 'bulk_update': $input = json_decode(file_get_contents('php://input'), true); $components = $input['components'] ?? []; $updated = 0; foreach ($components as $c) { $k = $c['key'] ?? ''; if (!$k) continue; $s = $c['status'] ?? 'online'; $v = $c['value'] ?? null; $e = $c['extra'] ?? null; $row = $pdo->prepare("SELECT extra_data FROM monitoring_components WHERE component_key=?"); $row->execute([$k]); $r = $row->fetch(); $cur = json_decode($r['extra_data'] ?? '{}', true) ?: []; if ($e && is_array($e)) $cur = array_merge($cur, $e); $pdo->prepare("UPDATE monitoring_components SET status=?, value=?, extra_data=?, last_seen=NOW() WHERE component_key=?") ->execute([$s, $v, json_encode($cur), $k]); $updated++; } echo json_encode(['success'=>true,'updated'=>$updated]); break; default: echo json_encode(['success'=>false,'error'=>'Invalid action', 'actions'=>['status','update','bulk_update','history']]); }