From 984c87481b892401bc61224da964a0e0851bd3d3 Mon Sep 17 00:00:00 2001 From: nekopride <158025518+nekopride@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:35:39 +0700 Subject: [PATCH] first commit --- kodingan_Node-Red.json | 1900 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1900 insertions(+) create mode 100644 kodingan_Node-Red.json diff --git a/kodingan_Node-Red.json b/kodingan_Node-Red.json new file mode 100644 index 0000000..d6833c0 --- /dev/null +++ b/kodingan_Node-Red.json @@ -0,0 +1,1900 @@ +[ + { + "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" + } + } +] \ No newline at end of file