<?php



header('Content-Type: text/html; charset=utf-8');



$ADMIN_PASS = "1234";

$pass = $_GET["pass"] ?? "";

$mac  = $_GET["mac"] ?? "";



if ($pass !== $ADMIN_PASS) {

  http_response_code(401);

  echo "401 - senha incorreta";

  exit;

}



if ($mac === "") {

  http_response_code(400);

  echo "MAC ausente";

  exit;

}



$dbPath     = __DIR__ . "/storage/devices_db.json";

$reportsDir = __DIR__ . "/storage/reports";



$db = [];

if (file_exists($dbPath)) {

  $tmp = json_decode(file_get_contents($dbPath), true);

  if (is_array($tmp)) $db = $tmp;

}



$rec = $db[$mac] ?? [];



$reportFile = $reportsDir . "/" . str_replace(":", "-", $mac) . ".json";

$report = null;



if (file_exists($reportFile)) {

  $tmp = json_decode(file_get_contents($reportFile), true);

  if (is_array($tmp)) $report = $tmp;

}



$received_at = $report["received_at"] ?? "";

$fw   = $report["data"]["fw"] ?? [];
$avail = [
  "esp_app" => (string)($rec["digilink_ver"] ?? ""),
  "esp_ota" => (string)($rec["digilink_ver"] ?? ""),
  "pic_app" => (string)($rec["cld24_ver"] ?? "")
];

$door = $report["data"]["door"] ?? [];



$online = false;

if ($received_at) {

  $t = strtotime($received_at);

  if ($t !== false && (time() - $t) <= 60) $online = true;

}



$open    = (int)($door["open"] ?? 0);

$closed  = (int)($door["closed"] ?? 0);

$opening = (int)($door["opening"] ?? 0);

$closing = (int)($door["closing"] ?? 0);



$door_state = "DESCONHECIDO";

$door_class = "";



if ($opening || $closing) { $door_state = "MOVENDO"; $door_class = "warn"; }

elseif ($open)            { $door_state = "ABERTA";  $door_class = "ok"; }

elseif ($closed)          { $door_state = "FECHADA"; $door_class = "bad"; }



$client = (string)($rec["note"] ?? "");



?>

<!doctype html>

<html lang="pt-BR">



<head>



<meta charset="utf-8">

<meta name="viewport" content="width=device-width, initial-scale=1">



<title>viso360 - Dispositivo</title>



<style>

:root{

  --bg:#0b1220;

  --card:rgba(255,255,255,0.06);

  --line:rgba(255,255,255,0.12);

  --text:rgba(255,255,255,0.92);

  --muted:rgba(255,255,255,0.7);

  --shadow:0 18px 40px rgba(0,0,0,0.35);

  --ok:#2bd576;

  --bad:#ff5c7a;

  --warn:#f6c445;

  --btn:rgba(255,255,255,0.10);

  --btnh:rgba(255,255,255,0.16);

  --r:16px;

}



*{box-sizing:border-box}



body{

  margin:0;

  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;

  background:

    radial-gradient(1200px 600px at 10% -10%, rgba(56,189,248,0.25), transparent 55%),

    radial-gradient(900px 500px at 90% 0%, rgba(168,85,247,0.22), transparent 50%),

    radial-gradient(900px 600px at 50% 120%, rgba(34,197,94,0.15), transparent 55%),

    var(--bg);

  color:var(--text);

}



.container{max-width:1100px;margin:26px auto;padding:0 16px;}



.card{

  border:1px solid var(--line);

  border-radius:var(--r);

  background:linear-gradient(180deg,var(--card),rgba(255,255,255,0.03));

  box-shadow:var(--shadow);

  overflow:hidden;

  margin-bottom:16px;

}



.header{

  padding:18px 20px;

  border-bottom:1px solid var(--line);

  display:flex;

  justify-content:space-between;

  gap:12px;

  align-items:flex-start;

}



h2{margin:0;font-size:18px}



.sub{margin-top:6px;font-size:12px;color:var(--muted)}



.badge{

  padding:8px 10px;

  border:1px solid var(--line);

  border-radius:999px;

  background:rgba(255,255,255,0.06);

  font-size:12px;

  color:var(--muted);

  white-space:nowrap;

}



.content{padding:18px 20px}



.btn{

  padding:10px 12px;

  border-radius:12px;

  border:1px solid var(--line);

  background:var(--btn);

  color:var(--text);

  cursor:pointer;

  font-weight:600;

  text-decoration:none;

  display:inline-block;

}



.btn:hover{background:var(--btnh)}



.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}



.pill{

  display:inline-flex;

  align-items:center;

  gap:8px;

  padding:6px 10px;

  border-radius:999px;

  border:1px solid var(--line);

  background:rgba(255,255,255,0.06);

  font-size:12px;

  color:var(--muted);

}



.dot{width:8px;height:8px;border-radius:999px;background:rgba(255,255,255,0.45)}



.pill.ok{border-color:rgba(43,213,118,0.35)} .pill.ok .dot{background:var(--ok)}

.pill.bad{border-color:rgba(255,92,122,0.35)} .pill.bad .dot{background:var(--bad)}

.pill.warn{border-color:rgba(246,196,69,0.35)} .pill.warn .dot{background:var(--warn)}



.grid{
  display:grid;
  grid-template-columns:1fr;
  gap:14px;
}

.table-grid{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:14px;
}



@media (max-width:900px){
  .grid{grid-template-columns:1fr}
  .table-grid{grid-template-columns:1fr}
}



.table{width:100%;border-collapse:collapse;}



.table td{

  padding:10px 8px;

  border-bottom:1px solid rgba(255,255,255,0.08);

  font-size:13px;

}



.k{color:var(--muted); width:240px}



.mono{

  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;

}



.small{font-size:12px;color:var(--muted);margin-top:8px}



</style>



</head>



<body>



<div class="container">



<div class="card">



<div class="header">



<div>

<h2>Painel do dispositivo</h2>

<div class="sub">Atualização rápida sem recarregar a página.</div>

</div>



<div class="row">

<a class="btn" href="admin.php?pass=<?php echo urlencode($pass); ?>">← Voltar</a>

<div class="badge"><?php echo htmlspecialchars($mac); ?></div>

</div>



</div>



<div class="content">



<div class="row" style="margin-bottom:10px">



<span id="pill_online" class="pill <?php echo $online ? "ok" : "bad"; ?>">

<span class="dot"></span>

<span id="txt_online"><?php echo $online ? "CONECTADO" : "DESCONECTADO"; ?></span>

</span>



<span id="pill_state" class="pill <?php echo $door_class ?: ""; ?>">

<span class="dot"></span>

<span id="txt_state"><?php echo htmlspecialchars($door_state); ?></span>

</span>



<?php if ($client !== ""): ?>

<span class="pill"><span class="dot"></span><?php echo htmlspecialchars($client); ?></span>

<?php endif; ?>



</div>



<div class="small">

Último relatório: <span class="mono" id="received_at"><?php echo htmlspecialchars($received_at ?: "sem relatório"); ?></span><br>

Arquivo: <span class="mono"><?php echo htmlspecialchars(basename($reportFile)); ?></span>

</div>



</div>

</div>



<div class="grid">



<div class="card">

<div class="header">

<div>

<h2>Firmware</h2>

<div class="sub">Reportado pelo dispositivo vs disponível no servidor</div>

</div>

<div class="badge">firmware</div>

</div>



<div class="content">



<table class="table">



<tr>
  <th class="k"></th>
  <th class="mono">Reportado</th>
  <th class="mono">Disponível</th>
</tr>

<tr>
  <td class="k">ESP APP</td>
  <td class="mono" id="fw_esp_app"><?php echo htmlspecialchars((string)($fw["esp_app"] ?? "-")); ?></td>
  <td class="mono" id="fw_esp_app_av"><?php echo htmlspecialchars(($avail["esp_app"] !== "" ? $avail["esp_app"] : "-")); ?></td>
</tr>

<tr>
  <td class="k">ESP OTA</td>
  <td class="mono" id="fw_esp_ota"><?php echo htmlspecialchars((string)($fw["esp_ota"] ?? "-")); ?></td>
  <td class="mono" id="fw_esp_ota_av"><?php echo htmlspecialchars(($avail["esp_ota"] !== "" ? $avail["esp_ota"] : "-")); ?></td>
</tr>

<tr>
  <td class="k">PIC APP</td>
  <td class="mono" id="fw_pic_app"><?php echo htmlspecialchars((string)($fw["pic_app"] ?? "-")); ?></td>
  <td class="mono" id="fw_pic_app_av"><?php echo htmlspecialchars(($avail["pic_app"] !== "" ? $avail["pic_app"] : "-")); ?></td>
</tr>



</table>



</div>

</div>



<div class="card">



<div class="header">



<div>

<h2>Porta</h2>

<div class="sub">Dados do bloco porta</div>

</div>



<div class="badge">porta</div>



</div>



<div class="content">



<div class="table-grid">
  <table class="table">
    <tr><td class="k">aberta</td><td class="mono" id="door_open"><?php echo htmlspecialchars((string)($door["open"] ?? "-")); ?></td></tr>
    <tr><td class="k">fechada</td><td class="mono" id="door_closed"><?php echo htmlspecialchars((string)($door["closed"] ?? "-")); ?></td></tr>
    <tr><td class="k">abrindo</td><td class="mono" id="door_opening"><?php echo htmlspecialchars((string)($door["opening"] ?? "-")); ?></td></tr>
    <tr><td class="k">fechando</td><td class="mono" id="door_closing"><?php echo htmlspecialchars((string)($door["closing"] ?? "-")); ?></td></tr>
    <tr><td class="k">ciclos</td><td class="mono" id="door_cycles"><?php echo htmlspecialchars((string)($door["cycles"] ?? "-")); ?></td></tr>
  </table>
  <table class="table">
    <tr><td class="k">tempo_s</td><td class="mono" id="door_time_s"><?php echo htmlspecialchars((string)($door["time_s"] ?? "-")); ?></td></tr>
    <tr><td class="k">posição</td><td class="mono" id="door_pos"><?php echo htmlspecialchars((string)($door["pos"] ?? "-")); ?></td></tr>
    <tr><td class="k">fc_abre_pos</td><td class="mono" id="door_fc_open_pos"><?php echo htmlspecialchars((string)($door["fc_open_pos"] ?? "-")); ?></td></tr>
    <tr><td class="k">fc_fecha_pos</td><td class="mono" id="door_fc_close_pos"><?php echo htmlspecialchars((string)($door["fc_close_pos"] ?? "-")); ?></td></tr>
    <tr><td class="k">entradas</td><td class="mono" id="door_inputs_mask_bits">-</td></tr>
  </table>
</div>



</div>



</div>



</div>



</div>



<script>
(function(){
  const pass = <?php echo json_encode($pass); ?>;
  const mac  = <?php echo json_encode($mac); ?>;
  const initialReceivedAt = <?php echo json_encode($received_at ?: null); ?>;
  const initialInputsMask = <?php echo json_encode($door["inputs_mask"] ?? null); ?>;

  const pillOnline = document.getElementById("pill_online");
  const txtOnline  = document.getElementById("txt_online");
  const pillState  = document.getElementById("pill_state");
  const txtState   = document.getElementById("txt_state");

  const setText = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = (v ?? "-"); };

  function setPillClass(el, cls){
    if (!el) return;
    el.classList.remove("ok","bad","warn");
    if (cls) el.classList.add(cls);
  }

  function normalizeBit(v, fallback){
    if (v === 0 || v === 1) return v;
    if (v === "0" || v === "1") return Number(v);
    return fallback;
  }

  let lastDoor = {
    pos: null, open: null, closed: null, time_s: null,
    opening: null, closing: null, fc_open_pos: null, fc_close_pos: null, cycles: null,
    inputs_mask: null
  };

  function normalizeDoor(door){
    const d = door || {};
    const out = { ...lastDoor };
    out.open    = normalizeBit(d.open, out.open);
    out.closed  = normalizeBit(d.closed, out.closed);
    out.opening = normalizeBit(d.opening, out.opening);
    out.closing = normalizeBit(d.closing, out.closing);
    if (d.pos !== undefined) out.pos = d.pos;
    if (d.time_s !== undefined) out.time_s = d.time_s;
    if (d.fc_open_pos !== undefined) out.fc_open_pos = d.fc_open_pos;
    if (d.fc_close_pos !== undefined) out.fc_close_pos = d.fc_close_pos;
    if (d.cycles !== undefined) out.cycles = d.cycles;
    if (d.inputs_mask !== undefined) out.inputs_mask = d.inputs_mask;
    lastDoor = out;
    return out;
  }

  function computeDoorState(door){
    const opening = Number(door?.opening || 0);
    const closing = Number(door?.closing || 0);
    const open    = Number(door?.open || 0);
    const closed  = Number(door?.closed || 0);

    if (opening || closing) return {state:"MOVENDO", cls:"warn"};
    if (open)               return {state:"ABERTA",  cls:"ok"};
    if (closed)             return {state:"FECHADA", cls:"bad"};
    return {state:"DESCONHECIDO", cls:""};
  }

  function computeOnline(received_at){
    if (!received_at) return false;
    const t = Date.parse(received_at);
    if (Number.isNaN(t)) return false;
    return (Date.now() - t) <= 60_000;
  }

  function decodeInputsMask(mask){
    if (mask === null || mask === undefined || mask === "") return "-";
    let n;
    if (typeof mask === "number") n = mask;
    else if (typeof mask === "string") {
      const s = mask.trim().toLowerCase();
      if (s.startsWith("0b")) n = parseInt(s.slice(2), 2);
      else if (s.startsWith("0x")) n = parseInt(s.slice(2), 16);
      else n = parseInt(s, 10);
    } else {
      return "-";
    }
    if (!Number.isFinite(n)) return "-";
    const defs = [
      {name:"BA",   type:"NO"},
      {name:"BF",   type:"NO"},
      {name:"BE",   type:"NC"},
      {name:"BR",   type:"NC"},
      {name:"INTER",type:"NO"},
      {name:"AUX",  type:"NO"},
      {name:"FT1",  type:"NC"},
      {name:"FT2",  type:"NC"},
    ];
    return defs.map((d, i) => `${d.name}(${d.type})=${(n >> i) & 1}`).join("; ");
  }

  let lastReceivedAt = initialReceivedAt;

  function applyReport(j){
    if (!j || j.exists === false) return;

    if (j.received_at) {
      lastReceivedAt = j.received_at;
      setText("received_at", j.received_at);
    }

    const online = computeOnline(lastReceivedAt);
    setPillClass(pillOnline, online ? "ok" : "bad");
    if (txtOnline) txtOnline.textContent = online ? "CONECTADO" : "DESCONECTADO";

    const fw = j.fw || {};
    setText("fw_esp_app", fw.esp_app);
    setText("fw_esp_ota", fw.esp_ota);
    setText("fw_pic_app", fw.pic_app);

    const door = normalizeDoor(j.door);
    setText("door_pos", door.pos);
    setText("door_open", door.open);
    setText("door_closed", door.closed);
    setText("door_time_s", door.time_s);
    setText("door_opening", door.opening);
    setText("door_closing", door.closing);
    setText("door_fc_open_pos", door.fc_open_pos);
    setText("door_fc_close_pos", door.fc_close_pos);
    setText("door_cycles", door.cycles);
    setText("door_inputs_mask_bits", decodeInputsMask(door.inputs_mask));

    const ds = computeDoorState(door);
    setPillClass(pillState, ds.cls);
    if (txtState) txtState.textContent = ds.state;
  }

  // SSE
  const url = "device_stream.php?pass=" + encodeURIComponent(pass) + "&mac=" + encodeURIComponent(mac) + "&t=" + Date.now();
  const es = new EventSource(url);

  es.addEventListener("report", (ev) => {
    try { applyReport(JSON.parse(ev.data)); } catch(e) {}
  });

  // recomputa online mesmo sem novos relatórios
  setInterval(() => {
    const online = computeOnline(lastReceivedAt);
    setPillClass(pillOnline, online ? "ok" : "bad");
    if (txtOnline) txtOnline.textContent = online ? "CONECTADO" : "DESCONECTADO";
  }, 1000);

  if (initialInputsMask !== null && initialInputsMask !== "") {
    lastDoor.inputs_mask = initialInputsMask;
    setText("door_inputs_mask_bits", decodeInputsMask(initialInputsMask));
  }

  // fallback: se SSE cair, faz polling leve automaticamente
  let fallbackTimer = null;
  function startFallback(){
    if (fallbackTimer) return;
    fallbackTimer = setInterval(async ()=>{
      try{
        const r = await fetch("device_data.php?pass="+encodeURIComponent(pass)+"&mac="+encodeURIComponent(mac)+"&t="+Date.now(), {cache:"no-store"});
        const j = await r.json();
        if (j && j.ok && j.exists) applyReport(j);
      }catch(e){}
    }, 800);
  }

  es.onerror = () => startFallback();
})();
</script>


</body>

</html>
