TKK_E32232007/kodingan_Node-Red.json

1900 lines
55 KiB
JSON

[
{
"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"
}
}
]