diff --git a/api/mqtt_listener.php b/api/mqtt_listener.php index 5e33ae6..7ff97ee 100644 --- a/api/mqtt_listener.php +++ b/api/mqtt_listener.php @@ -59,7 +59,7 @@ $db = new PDO( /* ========================= * MQTT CONFIG * ========================= */ -$mqttHost = 'mqtt.xupas.mywire.org'; +$mqttHost = '192.168.10.106'; // CT106: broker com a frota TODA (bridge VPS esp/+/status both) $mqttUser = 'xupa'; $mqttPass = 'xupa'; @@ -73,6 +73,7 @@ $tlsSettings = (new ConnectionSettings()) ->setPassword($mqttPass) ->setUseTls(true) ->setTlsSelfSignedAllowed(true) + ->setTlsVerifyPeerName(false) ->setKeepAliveInterval(30) ->setConnectTimeout(5) ->setSocketTimeout(1) @@ -107,6 +108,8 @@ $subscribeTls = function () use ($mqttTls, $db, $deviceSecret) { $rssi = isset($json['rssi']) ? (int)$json['rssi'] : null; $broker = isset($json['broker']) ? (string)$json['broker'] : null; $brightness = isset($json['brightness']) ? (int)$json['brightness'] : null; + $fw = isset($json['fw']) ? (string)$json['fw'] : null; + $chip = isset($json['chip']) ? (string)$json['chip'] : null; $hmac = isset($json['hmac']) ? strtoupper((string)$json['hmac']) : null; if ($hmac === null || $uptime === null) { @@ -114,7 +117,11 @@ $subscribeTls = function () use ($mqttTls, $db, $deviceSecret) { return; } - $expected = strtoupper(hash_hmac('sha256', $uid . '|' . $uptime, $deviceSecret)); + $_m = $uid . '|' . $uptime; + $_eg = strtoupper(hash_hmac('sha256', $_m, $deviceSecret)); // global + $_dsec = hash_hmac('sha256', $uid, 'af2e8f1b5411d7df7c9b3dba45fa4ca4517329d28d2550d29be898d34b015952'); + $_ed = strtoupper(hash_hmac('sha256', $_m, $_dsec)); // Fase C por-device + $expected = hash_equals($_ed, (string)$hmac) ? $_ed : $_eg; // aceita global OU per-device if (!hash_equals($expected, $hmac)) { logmsg("STATUS HMAC INVÁLIDO $uid — ignorado", LOG_ERROR); @@ -124,10 +131,10 @@ $subscribeTls = function () use ($mqttTls, $db, $deviceSecret) { $stmt = $db->prepare(" INSERT INTO contadores (uid, entradas, saidas, entradas_total, saidas_total, last_seen, online, - uptime, heap, rssi, current_broker, brightness) + uptime, heap, rssi, current_broker, brightness, fw_ver, chip) VALUES (:uid, 0, 0, 0, 0, NOW(), 1, - :uptime, :heap, :rssi, :broker, :brightness) + :uptime, :heap, :rssi, :broker, :brightness, :fw, :chip) ON DUPLICATE KEY UPDATE last_seen=NOW(), online=1, @@ -135,7 +142,10 @@ $subscribeTls = function () use ($mqttTls, $db, $deviceSecret) { heap=COALESCE(:heap2, heap), rssi=COALESCE(:rssi2, rssi), current_broker=COALESCE(:broker2, current_broker), - brightness=COALESCE(:brightness2, brightness) + brightness=COALESCE(:brightness2, brightness), + fw_ver=COALESCE(:fw2, fw_ver), + chip=COALESCE(:chip2, chip), + auth_ok=IF(blocked=0,1,auth_ok) "); $stmt->execute([ 'uid' => $uid, @@ -149,12 +159,23 @@ $subscribeTls = function () use ($mqttTls, $db, $deviceSecret) { 'rssi2' => $rssi, 'broker2' => $broker, 'brightness2' => $brightness, + 'fw' => $fw, + 'fw2' => $fw, + 'chip' => $chip, + 'chip2' => $chip, ]); - $mqttTls->publish("esp/$uid/cmd", - json_encode(['cmd' => 'HEARTBEAT_OK']), 1, false); + $chk = $db->prepare("SELECT blocked FROM contadores WHERE uid=:uid"); + $chk->execute(['uid' => $uid]); + $row = $chk->fetch(PDO::FETCH_ASSOC); - logmsg("STATUS OK $uid uptime=$uptime heap=$heap rssi=$rssi broker=$broker → HEARTBEAT_OK"); + if (!$row || (int)($row['blocked'] ?? 0) === 0) { + $mqttTls->publish("esp/$uid/cmd", + json_encode(['cmd' => 'HEARTBEAT_OK']), 1, false); + logmsg("STATUS OK $uid uptime=$uptime heap=$heap rssi=$rssi broker=$broker → HEARTBEAT_OK"); + } else { + logmsg("STATUS BLOQUEADO $uid — sem HEARTBEAT_OK", LOG_ERROR); + } }, 0); diff --git a/api/ota_trigger.php b/api/ota_trigger.php new file mode 100644 index 0000000..71da943 --- /dev/null +++ b/api/ota_trigger.php @@ -0,0 +1,25 @@ +'url_required']); exit; } +if (!in_array($cmd, ['OTA_CHECK','OTA_URL'], true)) { http_response_code(400); echo json_encode(['error'=>'cmd_invalid']); exit; } + +$payload = json_encode(['cmd'=>$cmd,'url'=>$url], JSON_UNESCAPED_SLASHES); + +if ($uid === 'all') { + $rows = $pdo->query("SELECT uid FROM contadores WHERE blocked=0 AND online=1")->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $r) mqtt_publish("esp/{$r['uid']}/cmd", $payload); + echo json_encode(['ok'=>1,'sent'=>count($rows)]); +} else { + if (!$uid) { http_response_code(400); echo json_encode(['error'=>'uid_required']); exit; } + mqtt_publish("esp/$uid/cmd", $payload); + echo json_encode(['ok'=>1]); +} diff --git a/esp_devices.php b/esp_devices.php new file mode 100644 index 0000000..f2475a3 --- /dev/null +++ b/esp_devices.php @@ -0,0 +1,137 @@ +true,CURLOPT_TIMEOUT=>8]); + $r = json_decode(curl_exec($ch),true); curl_close($ch); + return $r['devices'] ?? []; +} + +function pmx_post(array $data): array { + $ch = curl_init(PMX_API); + curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>http_build_query($data),CURLOPT_TIMEOUT=>8]); + $r = json_decode(curl_exec($ch),true); curl_close($ch); + return $r ?? []; +} + +$notice = ''; $error = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $uid = preg_replace('/[^0-9A-Fa-f]/', '', $_POST['uid'] ?? ''); + if (!$uid) { $error = 'UID inválido.'; } + else { + $res = pmx_post($_POST); + if (!empty($res['ok'])) $notice = "[{$uid}] {$_POST['action']} OK"; + else $error = $res['error'] ?? 'Erro'; + } + $qs = $notice ? '?ok='.urlencode($notice) : ($error ? '?err='.urlencode($error) : ''); + header("Location: esp_devices.php$qs"); exit; +} + +if (isset($_GET['ok'])) $notice = htmlspecialchars($_GET['ok']); +if (isset($_GET['err'])) $error = htmlspecialchars($_GET['err']); + +$devices = pmx_get(); +function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES); } +?> + + + + + +ESP Devices — Proxmox + + + +
+ ← Painel +

ESP Devices — Proxmox broker

+ dispositivos +
+
+ +
+
+ + +

Sem dispositivos ou erro a ligar ao broker Proxmox.

+ + + + + + + + + + + + + + + + +
UIDBroker atualEstadoÚltimo contactoHeapRSSIAções
+ bloqueado + + aprovado +
+ +
+ +
+ + +
+ +
+
+
+
+ +
+ + diff --git a/esp_proxmox.php b/esp_proxmox.php new file mode 100644 index 0000000..f3379f5 --- /dev/null +++ b/esp_proxmox.php @@ -0,0 +1,146 @@ + PDO::ERRMODE_EXCEPTION]); + return $pdo->query(" + SELECT uid, online, blocked, current_broker, last_seen, + brightness, heap, rssi, fw_ver, fw_ver, + TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago_s + FROM contadores + ORDER BY uid ASC + ")->fetchAll(PDO::FETCH_ASSOC); + } catch (Throwable $e) { + return []; + } +} +function pmx_post(array $data): array { + $ch = curl_init(PMX_API); + curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>http_build_query($data),CURLOPT_TIMEOUT=>8]); + $r = json_decode(curl_exec($ch),true); curl_close($ch); + return $r ?? []; +} + +$msg = ''; $msgOk = true; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $uid = preg_replace('/[^0-9A-Fa-f]/', '', $_POST['uid'] ?? ''); + if (!$uid) { $msg = 'UID invalido.'; $msgOk = false; } + else { + $res = pmx_post($_POST); + if (!empty($res['ok'])) { $msg = "[$uid] {$_POST['action']} OK"; } + else { $msg = $res['error'] ?? 'Erro'; $msgOk = false; } + } + $qs = $msg ? ($msgOk ? '?ok='.urlencode($msg) : '?err='.urlencode($msg)) : ''; + header("Location: /esp_proxmox.php$qs"); exit; +} + +if (isset($_GET['ok'])) { $msg = htmlspecialchars($_GET['ok']); $msgOk = true; } +if (isset($_GET['err'])) { $msg = htmlspecialchars($_GET['err']); $msgOk = false; } + +$devices = pmx_get(); +$title = "ESP Devices — Proxmox"; +ob_start(); +?> + + + + +
+ + + +

Sem dispositivos ou erro a ligar ao broker Proxmox.

+ +
+
+

Dispositivos ()

+
+
+ + + + '; + $ago=$diff===null?'-':($diff<60?$diff.'s':($diff<3600?round($diff/60).'min':round($diff/3600).'h')).' atras'; + ?> + + + + + + + + + + + + +
UIDVersãoVersãoUltimo contactoOnlineEstadoBrilhoAçõesBroker

onlineofflinebloqueadoaprovado
+ > + +
+ + + + + + + + + + +
+
+
+ + + + +true,CURLOPT_SSL_VERIFYPEER=>false,CURLOPT_TIMEOUT=>8]); + $r = json_decode(curl_exec($ch),true); curl_close($ch); + return $r['devices'] ?? []; +} +function vps_post(array $data): array { + $ch = curl_init(VPS_API); + curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>http_build_query($data),CURLOPT_SSL_VERIFYPEER=>false,CURLOPT_TIMEOUT=>8]); + $r = json_decode(curl_exec($ch),true); curl_close($ch); + return $r ?? []; +} + +$msg = ''; $msgOk = true; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $uid = preg_replace('/[^0-9A-Fa-f]/', '', $_POST['uid'] ?? ''); + if (!$uid) { $msg = 'UID invalido.'; $msgOk = false; } + else { + $res = vps_post($_POST); + if (!empty($res['ok'])) { $msg = "[$uid] {$_POST['action']} OK"; } + else { $msg = $res['error'] ?? 'Erro'; $msgOk = false; } + } + $qs = $msg ? ($msgOk ? '?ok='.urlencode($msg) : '?err='.urlencode($msg)) : ''; + header("Location: /esp_vps.php$qs"); exit; +} + +if (isset($_GET['ok'])) { $msg = htmlspecialchars($_GET['ok']); $msgOk = true; } +if (isset($_GET['err'])) { $msg = htmlspecialchars($_GET['err']); $msgOk = false; } + +$devices = vps_get(); usort($devices, function($a,$b){ return strcmp((string)($a['uid']??''),(string)($b['uid']??'')); }); +$title = "ESP Devices — VPS"; +ob_start(); +?> + + + + +
+ + + +

Sem dispositivos ou erro a ligar a VPS.

+ +
+
+

Dispositivos ()

+
+
+ + + + '; + $ago=$diff===null?'-':($diff<60?$diff.'s':($diff<3600?round($diff/60).'min':round($diff/3600).'h')).' atras'; + ?> + + + + + + + + + + + + +
UIDVersãoVersãoOnlineEstadoUltimo contactoBrilhoBroker
onlineofflinebloqueadoaprovadopendente
+
+ > + +
+
+ + + + + + + + + + +
+
+
+ + + + + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + } + return $pdo; +} diff --git a/inc/layout.php b/inc/layout.php new file mode 100644 index 0000000..c3639e9 --- /dev/null +++ b/inc/layout.php @@ -0,0 +1,94 @@ + + + + + + +<?= isset($title) ? htmlspecialchars($title).' — Proxmox' : 'Proxmox' ?> + + + +
+
+ + + + Proxmox +
+
+ + 👤 + + ← Painel + + Sair + +
+
+
+ +
+ + diff --git a/ota.php b/ota.php new file mode 100644 index 0000000..0181562 --- /dev/null +++ b/ota.php @@ -0,0 +1,174 @@ +query(" + SELECT uid, chip, fw_ver, online, blocked, last_seen, + TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago_s + FROM contadores ORDER BY uid ASC +")->fetchAll(); + +$title = "OTA Firmware"; +ob_start(); +?> + + + + +
+ + +
+ + +
+
Manifest
+ +
+ +
+ + + MB   + sem ficheiro +
+ +
+ +
Nenhum manifest ainda.
+ +
+ + +
+
Upload S3
+
+ +
+ + +
+
+ + +
+ +
+
+ + +
+
Upload WROOM
+
+ +
+ + +
+
+ + +
+ +
+
+ +
+ + +
+
+

Dispositivos ()

+ + + +
+
+ + + + + + + + + + + + + + +
UIDChipVersao atualDisponivelOnline
+ + + + onlineoffline + + + +
+
+
+ + + + + +
+ +
+

Painel

+

Bem-vindo,

+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
Sair
+
+
+ +
+ + + +