[ { "id": "67eddebaafa49d22", "type": "tab", "label": "Flow 1", "disabled": false, "info": "", "env": [] }, { "id": "1ac5238bc8e86a32", "type": "mqtt in", "z": "67eddebaafa49d22", "name": "MQTT IN (aquamonn/data)", "topic": "aquamonn/data", "qos": "0", "datatype": "json", "broker": "bfc3b3d0112912d7", "nl": false, "rap": false, "inputs": 0, "x": 210, "y": 240, "wires": [ [ "fae8f9eddf47b0e8" ] ] }, { "id": "fae8f9eddf47b0e8", "type": "json", "z": "67eddebaafa49d22", "name": "Parse JSON", "property": "payload", "action": "obj", "pretty": false, "x": 410, "y": 240, "wires": [ [ "eb5c53304dede5d8", "fde1f8e9948ff9b3", "257786bc47f4472f" ] ] }, { "id": "bf9607cb418cb77c", "type": "function", "z": "67eddebaafa49d22", "name": "Cek Amonia ", "func": "// Ambil nilai amonia dari flow context\nlet amonia = Number(flow.get(\"amonia\")) || 0;\nlet maksimum = Number(flow.get(\"p_maks\")) || 2.0;\nlet auto = String(flow.get(\"auto\") || \"false\").toLowerCase();\nlet statusManual = Number(flow.get(\"relay_manual\")) || 0;\n\n// Logika kontrol pompa\nlet relayState = 0;\n\nif (auto === \"true\") {\n if (amonia > maksimum) {\n relayState = 1;\n node.warn(\"🚨 AMONIA BERLEBIH! Pompa ON\");\n } else {\n relayState = 0;\n node.log(\"✅ Amonia normal, Pompa OFF\");\n }\n} else {\n relayState = statusManual;\n node.log(\"👆 Mode manual, Pompa = \" + (relayState ? \"ON\" : \"OFF\"));\n}\n\n// Simpan ke flow context\nflow.set(\"relay\", relayState);\n\n// Kirim ke ESP32 (topic khusus pompa)\nmsg.payload = {\n pompa_auto_mode: (auto === \"true\"),\n batas_amonia: maksimum,\n pompa_command: relayState ? \"ON\" : \"OFF\"\n};\nmsg.topic = \"aquamonn/relay\";\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 730, "y": 320, "wires": [ [ "4209516a2282ed2f", "94b36373e7c0a702" ] ] }, { "id": "fde1f8e9948ff9b3", "type": "function", "z": "67eddebaafa49d22", "name": "pH", "func": "msg.payload = msg.payload.pH || null;\nmsg.topic = \"pH\";\n\nflow.set(\"pH\", msg.payload);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 550, "y": 240, "wires": [ [ "614f7212ee1c0723", "c172fef7c3246031", "227e27214ab882c2" ] ] }, { "id": "eb5c53304dede5d8", "type": "function", "z": "67eddebaafa49d22", "name": "Amonia", "func": "msg.payload = msg.payload.amonia || null;\nmsg.topic = \"amonia_total\";\n\nflow.set(\"amonia\", msg.payload);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 560, "y": 280, "wires": [ [ "9a2cd222e4412337", "bf9607cb418cb77c", "c172fef7c3246031", "227e27214ab882c2", "6e76986120cce23f" ] ] }, { "id": "257786bc47f4472f", "type": "function", "z": "67eddebaafa49d22", "name": "Suhu", "func": "// Contoh jika suhu ada di dalam msg.payload.suhu\nmsg.payload = msg.payload.suhu || null;\nmsg.topic = \"Suhu\";\n\nflow.set(\"suhu\", msg.payload);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 550, "y": 200, "wires": [ [ "e74efbe9e10e5303", "c172fef7c3246031", "227e27214ab882c2" ] ] }, { "id": "e220cd1c7b384424", "type": "change", "z": "67eddebaafa49d22", "name": "", "rules": [ { "t": "set", "p": "p_maks", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1080, "y": 1260, "wires": [ [] ] }, { "id": "4209516a2282ed2f", "type": "function", "z": "67eddebaafa49d22", "name": "Status relay", "func": "// Ambil status relay dari flow context\nlet relayState = Number(flow.get(\"relay\")) || 0;\n\n// Ubah jadi ON / OFF\nif (relayState === 1) {\n msg.payload = \"ON\";\n} else if (relayState === 0) {\n msg.payload = \"OFF\";\n} else {\n msg.payload = \"unknown\";\n}\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 890, "y": 360, "wires": [ [ "0e6815c586945467" ] ] }, { "id": "2c8f752891c86ff2", "type": "http in", "z": "67eddebaafa49d22", "name": "", "url": "/set-pmaks", "method": "post", "upload": true, "swaggerDoc": "", "x": 280, "y": 1260, "wires": [ [ "07f1dfec2ed7e826" ] ] }, { "id": "07f1dfec2ed7e826", "type": "function", "z": "67eddebaafa49d22", "name": "set parameter maks", "func": "var nilaiBaru = parseFloat(msg.payload.maks);\n\nif (!isNaN(nilaiBaru) && nilaiBaru >= 0.1 && nilaiBaru <= 100.0) {\n flow.set(\"p_maks\", nilaiBaru);\n msg.payload = nilaiBaru;\n} else {\n node.warn(\"Nilai di luar rentang yang diperbolehkan (0.1 - 100.0)\");\n return null;\n}\n\nreturn msg;\n\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 580, "y": 1260, "wires": [ [ "4ca05874370f0321", "3bf3670a4281b696" ] ] }, { "id": "3ecd2803f9f68396", "type": "http in", "z": "67eddebaafa49d22", "name": "", "url": "/get-data", "method": "get", "upload": false, "swaggerDoc": "", "x": 270, "y": 1140, "wires": [ [ "9ba31890b8a451cf" ] ] }, { "id": "09ce34fa2cc121bc", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 680, "y": 1140, "wires": [] }, { "id": "9ba31890b8a451cf", "type": "function", "z": "67eddebaafa49d22", "name": "nr ke flutter", "func": "msg.payload = {\n suhu: flow.get(\"suhu\"),\n ph: flow.get(\"pH\"),\n amonia: flow.get(\"amonia\"),\n relay: flow.get(\"relay\"),\n turbidity: flow.get(\"turbidity\"),\n distance: flow.get(\"distance\"),\n tinggi_air: flow.get(\"tinggi_air\")\n};\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 490, "y": 1140, "wires": [ [ "09ce34fa2cc121bc", "4b9ee9fd3c7deca6" ] ] }, { "id": "1ebae4590b28a2c9", "type": "debug", "z": "67eddebaafa49d22", "name": "debug 1", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1420, "y": 340, "wires": [] }, { "id": "44a0a9781ec82884", "type": "mqtt in", "z": "67eddebaafa49d22", "name": "MQTT IN (aquamonn/kekeruhan)", "topic": "aquamonn/kekeruhan", "qos": "0", "datatype": "json", "broker": "bfc3b3d0112912d7", "nl": false, "rap": false, "inputs": 0, "x": 190, "y": 760, "wires": [ [ "6122fc5ebc06e71e" ] ] }, { "id": "6122fc5ebc06e71e", "type": "json", "z": "67eddebaafa49d22", "name": "Parse JSON", "property": "payload", "action": "obj", "pretty": false, "x": 410, "y": 760, "wires": [ [ "5f4a8e3e12915b58", "66d2f5c55fcf730f" ] ] }, { "id": "5f4a8e3e12915b58", "type": "function", "z": "67eddebaafa49d22", "name": "turbidity", "func": "msg.payload = msg.payload.turbidity || null;\nmsg.topic = \"turbidity\";\n\nflow.set(\"turbidity\",msg.payload);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 580, "y": 740, "wires": [ [ "c172fef7c3246031", "227e27214ab882c2", "75d4ebf392abdea1", "6e76986120cce23f", "8980aa1777ac9774" ] ] }, { "id": "66d2f5c55fcf730f", "type": "function", "z": "67eddebaafa49d22", "name": "distance", "func": "msg.payload = msg.payload.distance || null;\nmsg.topic = \"distance\";\n\nflow.set(\"distance\", msg.payload);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 580, "y": 800, "wires": [ [ "227e27214ab882c2", "341937785cc480d3" ] ] }, { "id": "4ca05874370f0321", "type": "ui_numeric", "z": "67eddebaafa49d22", "name": "", "label": "P_mak kadar Amonia", "tooltip": "", "group": "4cdf3e91604f4b5c", "order": 1, "width": 0, "height": 0, "wrap": false, "passthru": false, "topic": "topic", "topicType": "msg", "format": "{{value}}", "min": "00.00", "max": "100.00", "step": "00.10", "className": "", "x": 860, "y": 1260, "wires": [ [ "e220cd1c7b384424" ] ] }, { "id": "0e6815c586945467", "type": "ui_text", "z": "67eddebaafa49d22", "group": "cda931dc01569f7c", "order": 4, "width": 12, "height": 1, "name": "Status Relay", "label": "Relay", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": "", "color": "#000000", "x": 1090, "y": 360, "wires": [] }, { "id": "e74efbe9e10e5303", "type": "ui_gauge", "z": "67eddebaafa49d22", "name": "Suhu", "group": "cda931dc01569f7c", "order": 3, "width": 4, "height": 3, "gtype": "wave", "title": "Suhu", "label": "°C", "format": "{{msg.payload}}", "min": "0", "max": "100", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 690, "y": 200, "wires": [] }, { "id": "614f7212ee1c0723", "type": "ui_gauge", "z": "67eddebaafa49d22", "name": "pH", "group": "cda931dc01569f7c", "order": 1, "width": 4, "height": 3, "gtype": "wave", "title": "pH", "label": "", "format": "{{msg.payload}}", "min": "0", "max": "14", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 710, "y": 240, "wires": [] }, { "id": "9a2cd222e4412337", "type": "ui_gauge", "z": "67eddebaafa49d22", "name": "Amonia", "group": "cda931dc01569f7c", "order": 2, "width": 4, "height": 3, "gtype": "wave", "title": "amonia", "label": "mg/L", "format": "{{msg.payload}}", "min": "0", "max": "100", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 720, "y": 280, "wires": [] }, { "id": "c172fef7c3246031", "type": "ui_chart", "z": "67eddebaafa49d22", "name": "", "group": "cda931dc01569f7c", "order": 5, "width": 0, "height": 0, "label": "diagram", "chartType": "line", "legend": "false", "xformat": "dd HH:mm", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "0", "ymax": "200", "removeOlder": "1", "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 860, "y": 220, "wires": [ [] ] }, { "id": "4b9ee9fd3c7deca6", "type": "debug", "z": "67eddebaafa49d22", "name": "debug 3", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 680, "y": 1080, "wires": [] }, { "id": "fd77a2e5286229bb", "type": "mqtt out", "z": "67eddebaafa49d22", "name": "MQTT OUT (aquamonn/relay1)", "topic": "aquamonn/relay1", "qos": "0", "retain": "false", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "bfc3b3d0112912d7", "x": 1490, "y": 780, "wires": [] }, { "id": "c09a60dd099d0aa8", "type": "rbe", "z": "67eddebaafa49d22", "name": "", "func": "rbei", "gap": "", "start": "", "inout": "out", "septopics": false, "property": "payload", "topi": "topic", "x": 1230, "y": 780, "wires": [ [ "fd77a2e5286229bb" ] ] }, { "id": "69a7497e51fb0f5b", "type": "change", "z": "67eddebaafa49d22", "name": "", "rules": [ { "t": "set", "p": "batas_kekeruhan", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1130, "y": 1320, "wires": [ [] ] }, { "id": "9bbc432f4cd9c29d", "type": "http in", "z": "67eddebaafa49d22", "name": "", "url": "/set-batas_kekeruhan", "method": "post", "upload": true, "skipBodyParsing": false, "swaggerDoc": "", "x": 320, "y": 1320, "wires": [ [ "b01e86bb965a9cbf" ] ] }, { "id": "b01e86bb965a9cbf", "type": "function", "z": "67eddebaafa49d22", "name": "set parameter maks kekeruhan ", "func": "var nilaiBaru = parseFloat(msg.payload.max_kekeruhan);\n\nif (!isNaN(nilaiBaru) && nilaiBaru >= 0.1 && nilaiBaru <= 500.0) {\n\n flow.set(\"batas_kekeruhan\", nilaiBaru);\n\n node.warn(\"✅ Batas kekeruhan diset: \" + nilaiBaru);\n\n msg.payload = nilaiBaru;\n\n} else {\n node.warn(\"Nilai di luar rentang yang diperbolehkan (0.1 - 400.0)\");\n return null;\n}\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 590, "y": 1320, "wires": [ [ "841b1f1ac2853ebf", "adcdbc88d26c1e33" ] ] }, { "id": "841b1f1ac2853ebf", "type": "ui_numeric", "z": "67eddebaafa49d22", "name": "", "label": "Parameter Maksimal kekeruhan", "tooltip": "", "group": "4cdf3e91604f4b5c", "order": 1, "width": 0, "height": 0, "wrap": false, "passthru": false, "topic": "topic", "topicType": "msg", "format": "{{value}}", "min": "00.00", "max": "500.00", "step": "01.00", "className": "", "x": 870, "y": 1320, "wires": [ [ "69a7497e51fb0f5b" ] ] }, { "id": "629d8859cb8bfd1a", "type": "change", "z": "67eddebaafa49d22", "name": "", "rules": [ { "t": "set", "p": "batas_distance", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1350, "y": 1380, "wires": [ [] ] }, { "id": "76a3866ccc228ded", "type": "http in", "z": "67eddebaafa49d22", "name": "", "url": "/set-batas_distance", "method": "post", "upload": true, "skipBodyParsing": false, "swaggerDoc": "", "x": 310, "y": 1380, "wires": [ [ "e46ab0cc98b35111" ] ] }, { "id": "e46ab0cc98b35111", "type": "function", "z": "67eddebaafa49d22", "name": "set parameter min jarak", "func": "var value1 = Number(msg.payload.min_jarak);\n\nif (isNaN(value1)) {\n node.error(\"Nilai min_jarak tidak valid\");\n return null;\n}\n\nflow.set(\"batas_distance\", value1);\n\nnode.warn(\"✅ Batas distance diset: \" + value1);\n\nmsg.payload = value1;\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 590, "y": 1380, "wires": [ [ "cf869f06daa73226", "cc4d36240559c7d4" ] ] }, { "id": "cf869f06daa73226", "type": "ui_numeric", "z": "67eddebaafa49d22", "name": "", "label": "batas tinggi air", "tooltip": "", "group": "4cdf3e91604f4b5c", "order": 1, "width": 0, "height": 0, "wrap": false, "passthru": false, "topic": "topic", "topicType": "msg", "format": "{{value}}", "min": "0", "max": "100", "step": "1", "className": "", "x": 840, "y": 1380, "wires": [ [ "629d8859cb8bfd1a" ] ] }, { "id": "1190c7b944689348", "type": "http in", "z": "67eddebaafa49d22", "name": "", "url": "/get_history", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 280, "y": 1200, "wires": [ [ "ea9a44a9b4ebfe17" ] ] }, { "id": "ea9a44a9b4ebfe17", "type": "function", "z": "67eddebaafa49d22", "name": "query d history", "func": "let tanggal = msg.req.query.date;\n\nif (tanggal) {\n msg.topic = `\n SELECT\n id,\n suhu,\n ph,\n amonia,\n turbidity AS kekeruhan,\n distance,\n tinggiAir,\n waktu AS time,\n lampu\n FROM data_sensor\n WHERE DATE(waktu)='${tanggal}'\n ORDER BY waktu DESC\n `;\n} else {\n msg.topic = `\n SELECT\n id,\n suhu,\n ph,\n amonia,\n turbidity AS kekeruhan,\n distance,\n tinggiAir,\n waktu AS time,\n lampu\n FROM data_sensor\n ORDER BY waktu DESC\n `;\n}\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 560, "y": 1200, "wires": [ [ "971ddbe10f49f29f" ] ] }, { "id": "971ddbe10f49f29f", "type": "mysql", "z": "67eddebaafa49d22", "mydb": "91b465b9fa2c994f", "name": "", "x": 730, "y": 1200, "wires": [ [ "ce7a5b6faf32d2cd" ] ] }, { "id": "ce7a5b6faf32d2cd", "type": "json", "z": "67eddebaafa49d22", "name": "", "property": "payload", "action": "str", "pretty": false, "x": 870, "y": 1200, "wires": [ [ "2e9b3ea41f7235d5" ] ] }, { "id": "2e9b3ea41f7235d5", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 1020, "y": 1200, "wires": [] }, { "id": "101cb65acc03c85e", "type": "comment", "z": "67eddebaafa49d22", "name": "Dashboard Monitoring", "info": "Dashboard Monitoring", "x": 200, "y": 200, "wires": [] }, { "id": "a1", "type": "inject", "z": "67eddebaafa49d22", "name": "Contoh Data", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "payload": "{\"sensor_id\":\"sensor01\", \"suhu\":30.5, \"waktu\":\"2025-06-01 12:00:00\"}", "payloadType": "json", "x": 770, "y": 500, "wires": [ [ "a2" ] ] }, { "id": "227e27214ab882c2", "type": "function", "z": "67eddebaafa49d22", "name": "Data ", "func": "// Fungsi untuk merubah ke angka dengan aman\nfunction toNum(val) {\n let n = parseFloat(val);\n return isNaN(n) ? 0 : n;\n}\n\n// Ambil data sensor\nlet suhu = toNum(msg.payload?.suhu || flow.get(\"suhu\"));\nlet ph = toNum(msg.payload?.ph || flow.get(\"pH\"));\nlet amonia = toNum(msg.payload?.amonia || flow.get(\"amonia\"));\nlet turbidity = toNum(msg.payload?.turbidity || flow.get(\"turbidity\"));\nlet distance = toNum(msg.payload?.distance || flow.get(\"distance\"));\nlet tinggiAir = toNum(msg.payload?.tinggiAir || flow.get(\"tinggi_air\"));\n\n// ================= STATUS LAMPU =================\nlet statusLampu = flow.get(\"lampu\");\n\n// Debug nilai asli\nnode.warn(\"Nilai lampu asli: \" + statusLampu);\n\n// Konversi status\nif (\n statusLampu == 1 ||\n statusLampu == \"1\" ||\n statusLampu == \"on\" ||\n statusLampu == \"ON\" ||\n statusLampu === true\n) {\n\n statusLampu = \"ON\";\n\n} else {\n\n statusLampu = \"OFF\";\n\n}\n\n// Payload\nmsg.payload = {\n suhu: suhu,\n ph: ph,\n amonia: amonia,\n turbidity: turbidity,\n distance: distance,\n tinggiAir: tinggiAir,\n lampu: statusLampu,\n waktu: new Date().toLocaleString()\n};\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 790, "y": 460, "wires": [ [ "a2" ] ] }, { "id": "2ac289b311f822de", "type": "delay", "z": "67eddebaafa49d22", "name": "", "pauseType": "rate", "timeout": "1", "timeoutUnits": "minutes", "rate": "1", "nbRateUnits": "5", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 1240, "y": 460, "wires": [ [ "a3" ] ] }, { "id": "a3", "type": "mysql", "z": "67eddebaafa49d22", "mydb": "91b465b9fa2c994f", "name": "Simpan ke MySQL", "x": 1450, "y": 460, "wires": [ [ "1ebae4590b28a2c9" ] ] }, { "id": "a2", "type": "function", "z": "67eddebaafa49d22", "name": "Buat Query", "func": "// Mengambil data langsung dari msg.payload\nvar data = msg.payload;\n\n// Query SQL\nmsg.topic = `\nINSERT INTO data_sensor \n(suhu, ph, amonia, turbidity, distance, tinggiAir, lampu, waktu) \nVALUES (?, ?, ?, ?, ?, ?, ?, NOW())\n`;\n\n// Parameter MySQL\nmsg.payload = [\n parseFloat(data.suhu),\n parseFloat(data.ph),\n parseFloat(data.amonia),\n parseFloat(data.turbidity),\n parseFloat(data.distance),\n parseFloat(data.tinggiAir),\n data.lampu\n];\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 930, "y": 460, "wires": [ [ "41d6eb4fe0f07296", "19c6ea6e7225cca3" ] ] }, { "id": "41d6eb4fe0f07296", "type": "debug", "z": "67eddebaafa49d22", "name": "debug 4", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 1080, "y": 500, "wires": [] }, { "id": "19c6ea6e7225cca3", "type": "rbe", "z": "67eddebaafa49d22", "name": "", "func": "rbei", "gap": "", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 1090, "y": 460, "wires": [ [ "2ac289b311f822de" ] ] }, { "id": "b870d39796f44547", "type": "comment", "z": "67eddebaafa49d22", "name": "penyimpanan ke database", "info": "", "x": 590, "y": 460, "wires": [] }, { "id": "5aa05bbf5c017cc6", "type": "comment", "z": "67eddebaafa49d22", "name": "Pompa Otomasi (amonia, kekruhan dan Volume)", "info": "", "x": 880, "y": 720, "wires": [] }, { "id": "ad6bc97e3db948bf", "type": "comment", "z": "67eddebaafa49d22", "name": "Dashboard Monitoring", "info": "Dashboard Monitoring", "x": 180, "y": 720, "wires": [] }, { "id": "05fd7be03f3d40f0", "type": "firebase cloud messaging", "z": "67eddebaafa49d22", "name": "FCM aplikasi", "admin": "88242af294bbf4e8", "kind": "notification", "x": 1130, "y": 620, "wires": [ [] ] }, { "id": "bfd5c8e23790ad5d", "type": "function", "z": "67eddebaafa49d22", "name": "token nfc", "func": "msg.token = \"dleP_pdMRpqt1EPl_8sTw4:APA91bFBiOeFEKXOR-k9yN3M8mXMa44AWUVlZceYnjxgmp_l2Ewvww5xJt7SBwBH99ZqocWGIf6UDDrGRDsgW_RwnYhrnXMOXSEd6DjgEpQ99JZYYr1weuY\";\n\nmsg.payload = {\n title: \"Tes Aquamon\",\n body: \"Notifikasi dari Node-RED\"\n};\n\ndelete msg.topic;\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 960, "y": 620, "wires": [ [ "05fd7be03f3d40f0" ] ] }, { "id": "8980aa1777ac9774", "type": "debug", "z": "67eddebaafa49d22", "name": "debug 6", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 980, "y": 960, "wires": [] }, { "id": "8ca55be705adf870", "type": "inject", "z": "67eddebaafa49d22", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "test", "payloadType": "str", "x": 830, "y": 620, "wires": [ [ "bfd5c8e23790ad5d" ] ] }, { "id": "94b36373e7c0a702", "type": "function", "z": "67eddebaafa49d22", "name": "Notif amonia", "func": "let amonia = Number(flow.get(\"amonia\")) || 0;\nlet maksimum = Number(flow.get(\"p_maks\")) || 2.0;\n\nlet sudahKirim = flow.get(\"notif_amonia\") || false;\n\nif (amonia > maksimum) {\n\n if (!sudahKirim) {\n\n flow.set(\"notif_amonia\", true);\n\n msg.token = \"dleP_pdMRpqt1EPl_8sTw4:APA91bFBiOeFEKXOR-k9yN3M8mXMa44AWUVlZceYnjxgmp_l2Ewvww5xJt7SBwBH99ZqocWGIf6UDDrGRDsgW_RwnYhrnXMOXSEd6DjgEpQ99JZYYr1weuY\";\n\n msg.payload = {\n title: \"Peringatan Aquamon\",\n body: `Kadar amonia tinggi (${Math.floor(amonia*10) / 10} mg/L)`\n };\n\n return msg;\n }\n\n} else {\n\n flow.set(\"notif_amonia\", false);\n}\n\nreturn null;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 890, "y": 400, "wires": [ [ "05fd7be03f3d40f0" ] ] }, { "id": "63389c6186af5749", "type": "function", "z": "67eddebaafa49d22", "name": "Notif kekeruhan", "func": "let turbidity = Number(msg.turbidity || flow.get(\"turbidity\") || 0);\nlet batas = Number(flow.get(\"batas_kekeruhan\")) || 50;\n\nlet sudahKirim = flow.get(\"notif_kekeruhan\") || false;\n\nif (turbidity > batas) {\n\n if (!sudahKirim) {\n\n flow.set(\"notif_kekeruhan\", true);\n\n msg.token = \"dleP_pdMRpqt1EPl_8sTw4:APA91bFBiOeFEKXOR-k9yN3M8mXMa44AWUVlZceYnjxgmp_l2Ewvww5xJt7SBwBH99ZqocWGIf6UDDrGRDsgW_RwnYhrnXMOXSEd6DjgEpQ99JZYYr1weuY\";\n\n msg.payload = {\n title: \"Peringatan Aquamon\",\n body: `Kekeruhan tinggi (${Math.floor(turbidity*10) / 10} NTU)`\n };\n\n return msg;\n }\n\n} else {\n\n flow.set(\"notif_kekeruhan\", false);\n}\n\nreturn null;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1280, "y": 700, "wires": [ [ "05fd7be03f3d40f0" ] ] }, { "id": "6e76986120cce23f", "type": "function", "z": "67eddebaafa49d22", "name": "kontrol kekeruhan, jarak, dan amoniak", "func": "// =========================\n// Ambil Data Sensor\n// =========================\nlet turbidity = Number(msg.turbidity || flow.get(\"turbidity\") || 0);\nlet tinggiAir = Number(flow.get(\"tinggi_air\") || 0);\nlet amonia = Number(flow.get(\"amonia\") || 0);\n\n// =========================\n// Ambil Batas\n// =========================\nlet batasKekeruhan = Number(flow.get(\"batas_kekeruhan\")) || 0;\n\n// Tinggi air yang diinginkan (cm)\nlet batasTinggiAir = Number(flow.get(\"batas_distance\")) || 0;\n\n// Batas amonia\nlet batasAmonia = Number(flow.get(\"p_maks\")) || 0;\n\n// =========================\n// Status Pompa\n// =========================\nlet pompaMasuk = 0;\nlet pompaKeluar = 0;\n\n// =========================\n// PRIORITAS LEVEL AIR\n// =========================\n\n// Air kurang → isi air\nif (tinggiAir < batasTinggiAir) {\n\n pompaMasuk = 1;\n pompaKeluar = 0;\n\n node.warn(\"💧 AIR KURANG - Tinggi Air: \" + tinggiAir + \" cm\");\n\n}\n// Tinggi air sudah memenuhi → cek kualitas air\nelse {\n\n // Jika kekeruhan atau amonia melebihi batas\n if (turbidity > batasKekeruhan || amonia > batasAmonia) {\n\n pompaMasuk = 0;\n pompaKeluar = 1;\n\n if (turbidity > batasKekeruhan && amonia > batasAmonia) {\n node.warn(\"⚠️ AIR KERUH & AMONIA TINGGI - Ganti air\");\n }\n else if (turbidity > batasKekeruhan) {\n node.warn(\"⚠️ AIR KERUH - Ganti air\");\n }\n else {\n node.warn(\"🚨 AMONIA TINGGI - Ganti air\");\n }\n\n }\n else {\n\n pompaMasuk = 0;\n pompaKeluar = 0;\n\n node.warn(\"✅ Kondisi Normal\");\n }\n}\n\n// =========================\n// Simpan Status\n// =========================\nflow.set(\"pompa_masuk\", pompaMasuk);\nflow.set(\"pompa_keluar\", pompaKeluar);\n\n// =========================\n// Kirim ke ESP32\n// =========================\nmsg.payload = {\n pompa_masuk: pompaMasuk,\n pompa_keluar: pompaKeluar,\n turbidity: turbidity,\n tinggi_air: tinggiAir,\n amonia: amonia\n};\n\nmsg.topic = \"aquamonn/relay1\";\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 990, "y": 780, "wires": [ [ "c09a60dd099d0aa8", "63389c6186af5749" ] ] }, { "id": "get-status-in", "type": "http in", "z": "67eddebaafa49d22", "url": "/get-status", "method": "get", "x": 2380, "y": 1700, "wires": [ [ "get-status-fn" ] ] }, { "id": "get-status-fn", "type": "function", "z": "67eddebaafa49d22", "name": "", "func": "const s = flow.get('device_status') || {};\nconst schedule = flow.get('schedule') || {};\n\nmsg.payload = {\n relay: s.relay ?? 0,\n pompa_mode: s.pompa_mode ?? 'MANUAL',\n\n batas_amonia: flow.get('p_maks') ?? 0.5,\n batas_turbidity: flow.get('batas_kekeruhan') ?? 0,\n batas_distance: flow.get('batas_distance') ?? 0,\n tinggi_aquarium: flow.get('tinggi_aquarium') ?? 0,\n\n lampu: s.lampu ?? 0,\n lampu_mode: s.lampu_mode ?? 'MANUAL',\n\n lampu_on_time: schedule.on_time ?? '06:00',\n lampu_off_time: schedule.off_time ?? '18:00',\n schedule_enabled: schedule.enabled ?? false\n};\n\nmsg.statusCode = 200;\nmsg.headers = {\n \"Content-Type\": \"application/json\"\n};\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2660, "y": 1700, "wires": [ [ "get-status-out" ] ] }, { "id": "get-status-out", "type": "http response", "z": "67eddebaafa49d22", "x": 2850, "y": 1700, "wires": [] }, { "id": "75d4ebf392abdea1", "type": "ui_gauge", "z": "67eddebaafa49d22", "name": "turbidity", "group": "cda931dc01569f7c", "order": 3, "width": 4, "height": 3, "gtype": "wave", "title": "turbidity", "label": "NTU", "format": "{{msg.payload}}", "min": "0", "max": "150", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 600, "y": 660, "wires": [] }, { "id": "f482a0176d55f712", "type": "ui_gauge", "z": "67eddebaafa49d22", "name": "distance", "group": "cda931dc01569f7c", "order": 3, "width": 4, "height": 3, "gtype": "wave", "title": "tinggi air", "label": "cm", "format": "{{msg.payload}}", "min": "0", "max": "200", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 960, "y": 880, "wires": [] }, { "id": "ff08ce828a8ab3cb", "type": "ui_form", "z": "67eddebaafa49d22", "name": "Setting Jadwal", "label": "⚙️ SETTING JADWAL", "group": "a94dd1f096ac2c9b", "order": 1, "width": 6, "height": 3, "options": [ { "label": "Jam NYALA (contoh: 06:00)", "value": "on_time", "type": "text", "required": true, "rows": null }, { "label": "Jam MATI (contoh: 18:00)", "value": "off_time", "type": "text", "required": true, "rows": null } ], "formValue": { "on_time": "", "off_time": "" }, "payload": "", "submit": "set jadwal", "cancel": "", "topic": "", "topicType": "str", "splitLayout": false, "className": "", "x": 2040, "y": 380, "wires": [ [ "7969fbc0ae2282a3", "33fa2cfc4d307cc0" ] ] }, { "id": "7969fbc0ae2282a3", "type": "function", "z": "67eddebaafa49d22", "name": "Proses & Kirim Jadwal", "func": "var onParts = msg.payload.on_time.split(\":\");\nvar offParts = msg.payload.off_time.split(\":\");\n\nmsg.payload = {\n auto_mode: true,\n on_hour: parseInt(onParts[0]),\n on_minute: parseInt(onParts[1]),\n off_hour: parseInt(offParts[0]),\n off_minute: parseInt(offParts[1])\n};\n\nmsg.topic = \"aquamonn/schedule\";\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2240, "y": 420, "wires": [ [ "3482f4c78fef1932" ] ] }, { "id": "3482f4c78fef1932", "type": "mqtt out", "z": "67eddebaafa49d22", "name": "MQTT OUT(aquamon/schedule)", "topic": "aquamonn/schedule", "qos": "1", "retain": false, "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "bfc3b3d0112912d7", "x": 2490, "y": 420, "wires": [] }, { "id": "da019ce3e84bd30b", "type": "http in", "z": "67eddebaafa49d22", "name": "API Schedule", "url": "/api-schedule", "method": "post", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 1670, "y": 420, "wires": [ [ "33fa2cfc4d307cc0" ] ] }, { "id": "2320ac246a8bc22b", "type": "http response", "z": "67eddebaafa49d22", "name": "", "statusCode": "200", "headers": { "Content-Type": "application/json" }, "x": 2040, "y": 440, "wires": [] }, { "id": "b33a6e46b2fca1f9", "type": "mqtt out", "z": "67eddebaafa49d22", "name": "", "topic": "aquamonn/lampu", "qos": "1", "retain": false, "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "bfc3b3d0112912d7", "x": 2330, "y": 500, "wires": [] }, { "id": "962bb23a21c18f87", "type": "ui_text", "z": "67eddebaafa49d22", "group": "a94dd1f096ac2c9b", "order": 4, "width": 6, "height": 1, "name": "", "label": "📊 STATUS", "format": "{{msg.payload}}", "layout": "row-center", "x": 2270, "y": 600, "wires": [] }, { "id": "16a71d81f5f2d986", "type": "mqtt in", "z": "67eddebaafa49d22", "name": "", "topic": "aquamonn/lampu_status", "qos": 0, "datatype": "json", "broker": "bfc3b3d0112912d7", "nl": false, "rap": false, "inputs": 0, "x": 1870, "y": 600, "wires": [ [ "d8e36ec0aeedf2ce", "d357f3c50b1b7cb1" ] ] }, { "id": "d8e36ec0aeedf2ce", "type": "function", "z": "67eddebaafa49d22", "name": "Format Status", "func": "\nlet s = msg.payload;\nlet schedule = flow.get(\"schedule\") || {};\nlet on_time = schedule.on_time || \"--:--\";\nlet off_time = schedule.off_time || \"--:--\";\n\n\nlet status = s.lampu_status || \"OFF\";\nlet mode = s.lampu_mode || flow.get(\"schedule\")?.enabled ? \"AUTO\" : \"MANUAL\";\nmsg.payload =\n `💡 Lampu: ${status} | Mode: ${mode} | Jadwal: ${on_time}-${off_time}`;\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2090, "y": 600, "wires": [ [ "962bb23a21c18f87" ] ] }, { "id": "33fa2cfc4d307cc0", "type": "function", "z": "67eddebaafa49d22", "name": "Proses API", "func": "// Data dari Flutter\nlet on_time = msg.payload.on_time;\nlet off_time = msg.payload.off_time;\nlet enabled = msg.payload.enabled;\n\n// Simpan sebagai setting terbaru\nflow.set(\"schedule\", {\n on_time: on_time,\n off_time: off_time,\n enabled: enabled\n});\n\n// Kirim ke node berikutnya\nmsg.payload = flow.get(\"schedule\")\n\nreturn msg;", "outputs": 2, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1830, "y": 420, "wires": [ [ "ff08ce828a8ab3cb", "7969fbc0ae2282a3", "603e2c200cd94f96" ], [ "2320ac246a8bc22b" ] ] }, { "id": "1edff568b079766b", "type": "ui_switch", "z": "67eddebaafa49d22", "name": "", "label": "lampu manual", "tooltip": "", "group": "a94dd1f096ac2c9b", "order": 0, "width": 0, "height": 0, "passthru": true, "decouple": "false", "topic": "topic", "topicType": "msg", "style": "", "onvalue": "1", "onvalueType": "num", "onicon": "", "oncolor": "", "offvalue": "0", "offvalueType": "num", "officon": "", "offcolor": "", "animate": false, "className": "", "x": 2120, "y": 500, "wires": [ [ "b33a6e46b2fca1f9" ] ] }, { "id": "62119e99371b7e14", "type": "http in", "z": "67eddebaafa49d22", "name": "API lampu", "url": "/api-lampu", "method": "post", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 1760, "y": 500, "wires": [ [ "abb6bbae9528a933" ] ] }, { "id": "b28dcbc36e97dcd9", "type": "http response", "z": "67eddebaafa49d22", "name": "", "statusCode": "200", "headers": { "Content-Type": "application/json" }, "x": 2100, "y": 540, "wires": [] }, { "id": "abb6bbae9528a933", "type": "function", "z": "67eddebaafa49d22", "name": "Proses API", "func": "var statusLampu = msg.payload.lampu;\n\n// Jika ON\nif (statusLampu === \"on\") {\n\n flow.set(\"lampu\", \"ON\");\n\n msg.payload = 1;\n return [msg, null];\n\n}\n\n// Jika OFF\nif (statusLampu === \"off\") {\n\n flow.set(\"lampu\", \"OFF\");\n\n msg.payload = 0;\n return [msg, null];\n\n}\n\n// Jika data salah\nmsg.statusCode = 400;\n\nmsg.payload = {\n status: \"error\",\n message: \"Gunakan on/off\"\n};\n\nreturn [null, msg];", "outputs": 2, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1910, "y": 500, "wires": [ [ "1edff568b079766b", "227e27214ab882c2", "4a32c5a52275c9de" ], [ "b28dcbc36e97dcd9" ] ] }, { "id": "a8240d8621d21ee4", "type": "comment", "z": "67eddebaafa49d22", "name": "Penjadwalan Lampu", "info": "", "x": 2030, "y": 340, "wires": [] }, { "id": "d357f3c50b1b7cb1", "type": "function", "z": "67eddebaafa49d22", "name": "Notif jadwal lampu", "func": "let status = (msg.payload.lampu_status || \"OFF\").toUpperCase();\n\nlet terakhir = flow.get(\"status_lampu_terakhir\");\n\nif (terakhir === status) {\n return null;\n}\n\nflow.set(\"status_lampu_terakhir\", status);\n\nmsg.token = \"dleP_pdMRpqt1EPl_8sTw4:APA91bFBiOeFEKXOR-k9yN3M8mXMa44AWUVlZceYnjxgmp_l2Ewvww5xJt7SBwBH99ZqocWGIf6UDDrGRDsgW_RwnYhrnXMOXSEd6DjgEpQ99JZYYr1weuY\";\n\nmsg.payload = {\n title: \"Lampu Aquascape\",\n body: status === \"ON\"\n ? \"Lampu menyala\"\n : \"Lampu mati\"\n};\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2090, "y": 660, "wires": [ [ "05fd7be03f3d40f0" ] ] }, { "id": "d7fa5aa55c52ab42", "type": "comment", "z": "67eddebaafa49d22", "name": "Pompa otomasi (Kadar Amonia)", "info": "", "x": 990, "y": 280, "wires": [] }, { "id": "35242a5545433aad", "type": "http in", "z": "67eddebaafa49d22", "name": "", "url": "/set-tinggi_aquarium", "method": "post", "upload": true, "skipBodyParsing": false, "swaggerDoc": "", "x": 310, "y": 1440, "wires": [ [ "b806c84ab5bb9fc9" ] ] }, { "id": "b806c84ab5bb9fc9", "type": "function", "z": "67eddebaafa49d22", "name": "tinggi_aquarium", "func": "var nilaiBaru = parseFloat(msg.payload.tinggi_aquarium);\n\nif (!isNaN(nilaiBaru) && nilaiBaru >= 1 && nilaiBaru <= 100) {\n\n flow.set(\"tinggi_aquarium\", nilaiBaru);\n\n node.warn(\"✅ Tinggi aquarium diset: \" + nilaiBaru + \" cm\");\n\n msg.payload = nilaiBaru;\n\n} else {\n\n node.warn(\"Nilai di luar rentang yang diperbolehkan (1 - 100 cm)\");\n return null;\n}\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 580, "y": 1440, "wires": [ [ "bc9026e0f0619982", "ea1b224a2531a2c3" ] ] }, { "id": "bc9026e0f0619982", "type": "ui_numeric", "z": "67eddebaafa49d22", "name": "", "label": "kontrol tinggi aquarium ", "tooltip": "", "group": "4cdf3e91604f4b5c", "order": 1, "width": 0, "height": 0, "wrap": false, "passthru": false, "topic": "topic", "topicType": "msg", "format": "{{value}}", "min": "00.00", "max": "100.00", "step": "01.00", "className": "", "x": 840, "y": 1440, "wires": [ [ "3c3148c4f01bb1e4" ] ] }, { "id": "3c3148c4f01bb1e4", "type": "change", "z": "67eddebaafa49d22", "name": "", "rules": [ { "t": "set", "p": "tinggi_aquarium", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1130, "y": 1440, "wires": [ [] ] }, { "id": "341937785cc480d3", "type": "function", "z": "67eddebaafa49d22", "name": "tinggi air", "func": "// Ambil tinggi aquarium\nlet H = Number(flow.get(\"tinggi_aquarium\") || 0);\n\n// Ambil distance\nlet distance = Number(flow.get(\"distance\") || 0);\n\n// Hitung tinggi air\nlet tinggiAir = H - distance;\n\n// Batasi hasil\ntinggiAir = Math.max(0, Math.min(H, tinggiAir));\n\n// Simpan ke flow\nflow.set(\"tinggi_air\", tinggiAir);\n\n// Debug\nnode.warn(\n \"Tinggi Aquarium: \" + H +\n \" cm | Distance: \" + distance +\n \" cm | Tinggi Air: \" + tinggiAir.toFixed(1) + \" cm\"\n);\n\n// Payload hanya tinggi_air\nmsg.payload = Number(tinggiAir.toFixed(1));\nmsg.topic = \"tinggi_air\";\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 760, "y": 820, "wires": [ [ "f482a0176d55f712", "6e76986120cce23f", "227e27214ab882c2" ] ] }, { "id": "ea1b224a2531a2c3", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 1360, "y": 1460, "wires": [] }, { "id": "cc4d36240559c7d4", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 1560, "y": 1340, "wires": [] }, { "id": "adcdbc88d26c1e33", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 1380, "y": 1280, "wires": [] }, { "id": "3bf3670a4281b696", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 1300, "y": 1200, "wires": [] }, { "id": "603e2c200cd94f96", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 2320, "y": 300, "wires": [] }, { "id": "4a32c5a52275c9de", "type": "http response", "z": "67eddebaafa49d22", "name": "http out", "statusCode": "", "headers": {}, "x": 2420, "y": 560, "wires": [] }, { "id": "bfc3b3d0112912d7", "type": "mqtt-broker", "name": "", "broker": "broker.emqx.io", "port": "1883", "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": 4, "keepalive": 60, "cleansession": true, "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthRetain": "false", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closeQos": "0", "closeRetain": "false", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willRetain": "false", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "" }, { "id": "4cdf3e91604f4b5c", "type": "ui_group", "name": "Kontrol ", "tab": "4423227bec07427f", "order": 2, "disp": true, "width": "6", "collapse": false, "className": "" }, { "id": "cda931dc01569f7c", "type": "ui_group", "name": "Monitoring", "tab": "4423227bec07427f", "order": 1, "disp": true, "width": 12, "collapse": false, "className": "" }, { "id": "91b465b9fa2c994f", "type": "MySQLdatabase", "name": "", "host": "localhost", "port": "3306", "db": "mydb", "tz": "", "charset": "UTF8" }, { "id": "88242af294bbf4e8", "type": "firebase admin", "name": "Aquamon" }, { "id": "a94dd1f096ac2c9b", "type": "ui_group", "name": "📅 Penjadwalan Lampu", "tab": "4423227bec07427f", "order": 3, "width": 12, "collapse": false }, { "id": "4423227bec07427f", "type": "ui_tab", "name": "Sistem Monitoring dan Kotroling Kualitas Air Pada Aquarium", "icon": "dashboard", "order": 1, "disabled": false, "hidden": false }, { "id": "a9716a8fd7ce5d5a", "type": "global-config", "env": [], "modules": { "node-red-dashboard": "3.6.6", "node-red-node-mysql": "3.0.0", "node-red-contrib-firebase-messaging": "0.4.2" } } ]