TKK_E32232338/assets/js/node-red.js

110 lines
4.5 KiB
JavaScript

(function () {
"use strict";
const POLL_MS = 3000;
const DATA_URL = (window.NODERED_URL || "").replace(/\/$/, "") + "/get-data";
const SENSORS = {
suhu: { valId:"val-suhu", barId:"bar-suhu", unit:"", decimals:1, max:45, color: v=>(v>32||v<18)?"#f59e0b":"#22c55e" },
ph: { valId:"val-ph", barId:"bar-ph", unit:" pH", decimals:2, max:14, color: v=>(v<6.5||v>8.5)?"#ef4444":"#22c55e" },
amonia: { valId:"val-amonia", barId:"bar-amonia", unit:" ppm", decimals:2, max:10, color: v=>v>2?"#ef4444":v>1?"#f59e0b":"#22c55e" },
kekeruhan: { valId:"val-kekeruhan", barId:"bar-kekeruhan", unit:" NTU", decimals:1, max:150, color: v=>v>80?"#ef4444":v>40?"#f59e0b":"#22c55e" },
distance: { valId:"val-distance", barId:"bar-distance", unit:" cm", decimals:1, max:100, color: v=>v>70?"#ef4444":v>50?"#f59e0b":"#22c55e" },
};
function updateSensor(key, raw) {
const cfg = SENSORS[key];
if (!cfg || raw===null || raw===undefined) return;
const v = parseFloat(raw);
if (isNaN(v)) return;
const valEl = document.getElementById(cfg.valId);
const barEl = document.getElementById(cfg.barId);
if (valEl) valEl.textContent = v.toFixed(cfg.decimals) + cfg.unit;
if (barEl) {
barEl.style.width = Math.min(100, Math.max(0, v/cfg.max*100)) + "%";
barEl.style.background = cfg.color(v);
}
}
function updateDevice(badgeId, valId, raw) {
const valEl = document.getElementById(valId);
const badgeEl = document.getElementById(badgeId);
if (!valEl || !badgeEl || raw===null || raw===undefined) return;
const on = (raw==1 || String(raw).toUpperCase()==="ON");
valEl.textContent = on ? "ON" : "OFF";
badgeEl.classList.toggle("on", on);
badgeEl.classList.toggle("off", !on);
}
const RULES = [
{key:"amonia", label:"Amonia", dangerHi:2, warnHi:1 },
{key:"suhu", label:"Suhu", dangerHi:33, warnHi:30, dangerLo:17, warnLo:20 },
{key:"ph", label:"pH", dangerLo:6.0, warnLo:6.5,dangerHi:9.0,warnHi:8.5},
{key:"kekeruhan", label:"Kekeruhan", dangerHi:80, warnHi:40 },
{key:"distance", label:"Distance", dangerHi:70, warnHi:50 }
];
function checkThresholds(data) {
const notifs = [];
RULES.forEach(r => {
const v = parseFloat(data[r.key]);
if (isNaN(v)) return;
if ((r.dangerHi!==undefined&&v>=r.dangerHi)||(r.dangerLo!==undefined&&v<=r.dangerLo))
notifs.push({level:"danger", msg:`⚠️ ${r.label} KRITIS: ${v.toFixed(2)}`});
else if ((r.warnHi!==undefined&&v>=r.warnHi)||(r.warnLo!==undefined&&v<=r.warnLo))
notifs.push({level:"warn", msg:`🔔 ${r.label} Abnormal: ${v.toFixed(2)}`});
});
const badge = document.querySelector(".notif-badge");
const listEl = document.querySelector("#notifDropdown ul");
if (!listEl) return;
if (!notifs.length) {
if (badge) badge.textContent = "0";
listEl.innerHTML = "<li style='color:rgba(255,255,255,.5)'>Semua normal ✔</li>";
} else {
if (badge) badge.textContent = notifs.length;
listEl.innerHTML = notifs.map(n=>
`<li style="color:${n.level==='danger'?'#ef4444':'#f59e0b'}">${n.msg}</li>`
).join("");
}
}
async function fetchData() {
try {
const res = await fetch(DATA_URL, {cache:"no-store"});
if (!res.ok) throw new Error("HTTP "+res.status);
const data = await res.json();
// normalize: turbidity → kekeruhan, pH → ph
const d = {
suhu: data.suhu ?? data.temperature ?? null,
ph: data.ph ?? data.pH ?? null,
amonia: data.amonia ?? null,
kekeruhan: data.kekeruhan ?? data.turbidity ?? null,
relay: data.relay ?? null,
lampu: data.lampu ?? null,
distance: data.distance ?? null
};
Object.keys(SENSORS).forEach(k => updateSensor(k, d[k]));
updateDevice("badge-relay", "val-relay", d.relay);
updateDevice("badge-lampu", "val-lampu", d.lampu);
checkThresholds(d);
if (typeof window.onSensorUpdate==="function") window.onSensorUpdate(true);
} catch(err) {
console.warn("[node-red.js]", err.message);
if (typeof window.onSensorUpdate==="function") window.onSensorUpdate(false);
}
}
if (!window.NODERED_URL) {
console.error("[node-red.js] window.NODERED_URL belum diset di monitoring.php!");
return;
}
fetchData();
setInterval(fetchData, POLL_MS);
})();