212 lines
10 KiB
PHP
212 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* API Monitoring - Backend for Arduino ESP32 Component Monitoring
|
|
* Endpoints:
|
|
* GET ?action=status → Get all component statuses with extra data
|
|
* POST ?action=update → Update a component from Arduino/ESP32
|
|
* GET ?action=history&id=X → Get history for a specific component
|
|
* POST ?action=bulk_update → Update multiple components at once
|
|
*/
|
|
|
|
header('Content-Type: application/json');
|
|
header('Access-Control-Allow-Origin: *');
|
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
|
header('Access-Control-Allow-Headers: Content-Type');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
|
|
|
|
include 'koneksi.php';
|
|
|
|
// ─── Create tables ──────────────────────────────────────────────────────────
|
|
$pdo->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']]);
|
|
}
|