110 lines
4.5 KiB
JavaScript
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);
|
|
|
|
})(); |