[ { "id": "tab1", "type": "tab", "label": "Dashboard Demo", "disabled": false, "info": "" }, { "id": "5f31529b3e749ebe", "type": "mqtt in", "z": "tab1", "name": "MQTT Sensor", "topic": "iot/sensor", "qos": "0", "datatype": "auto", "broker": "mqtt_broker_local", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 300, "y": 580, "wires": [ [ "e81e5db8c5529bb0" ] ] }, { "id": "e81e5db8c5529bb0", "type": "json", "z": "tab1", "name": "Parse JSON", "property": "payload", "action": "", "pretty": false, "x": 480, "y": 580, "wires": [ [ "a4575ed03e736231", "94dd8dde94285a13" ] ] }, { "id": "37d04d285e6d7864", "type": "function", "z": "tab1", "name": " Get Data Filter", "func": "// Simpan pilihan ke flow untuk digunakan tombol ambil data\nflow.set(\"sensor_selected\", msg.payload);\nreturn null;\n", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 540, "y": 780, "wires": [ [] ] }, { "id": "a4575ed03e736231", "type": "function", "z": "tab1", "name": "Split Sensor Data", "func": "return [\n { payload: msg.payload.weight },\n { payload: msg.payload.humidity },\n { payload: msg.payload.soil }\n];", "outputs": 3, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 690, "y": 560, "wires": [ [ "1815f02556163f1d", "6452681ddc0cd6cd" ], [ "edf401a2960b5562", "22b8f2858da8ce95" ], [ "8992134a9099bffa", "e03f9616d12ff1df" ] ] }, { "id": "5a0b35725a51d4dc", "type": "sqlite", "z": "tab1", "mydb": "sqlite_local", "sqlquery": "msg.topic", "sql": "", "name": "Simpan ke SQLite", "x": 810, "y": 700, "wires": [ [] ] }, { "id": "1815f02556163f1d", "type": "ui_gauge", "z": "tab1", "name": "Weight", "group": "ui_group_main", "order": 2, "width": 6, "height": 0, "gtype": "gage", "title": "Weight (g)", "label": "g", "format": "{{value}}", "min": 0, "max": "200", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "300", "seg2": "700", "diff": false, "className": "", "x": 890, "y": 520, "wires": [] }, { "id": "edf401a2960b5562", "type": "ui_gauge", "z": "tab1", "name": "Humidity", "group": "ui_group_main", "order": 1, "width": 6, "height": 0, "gtype": "gage", "title": "Humidity (%)", "label": "%", "format": "{{value}}", "min": 0, "max": "100", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "40", "seg2": "70", "diff": false, "className": "", "x": 900, "y": 560, "wires": [] }, { "id": "8992134a9099bffa", "type": "ui_gauge", "z": "tab1", "name": "Soil Moisture", "group": "ui_group_main", "order": 3, "width": 6, "height": 0, "gtype": "gage", "title": "Soil Moisture", "label": "%", "format": "{{value}}", "min": 0, "max": 100, "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "30", "seg2": "60", "diff": false, "className": "", "x": 910, "y": 600, "wires": [] }, { "id": "6452681ddc0cd6cd", "type": "ui_chart", "z": "tab1", "name": "Chart Weight", "group": "ui_group_main", "order": 5, "width": 6, "height": 0, "label": "Weight History", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "Waiting...", "dot": false, "ymin": "0", "ymax": "1000", "removeOlder": "3", "removeOlderPoints": "100", "removeOlderUnit": "60", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 1110, "y": 520, "wires": [ [] ] }, { "id": "22b8f2858da8ce95", "type": "ui_chart", "z": "tab1", "name": "Chart Humidity", "group": "ui_group_main", "order": 4, "width": 6, "height": 0, "label": "Humidity History", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "Waiting...", "dot": false, "ymin": "0", "ymax": "100", "removeOlder": "3", "removeOlderPoints": "100", "removeOlderUnit": "60", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#ff7f0e", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 1120, "y": 560, "wires": [ [] ] }, { "id": "e03f9616d12ff1df", "type": "ui_chart", "z": "tab1", "name": "Chart Soil", "group": "ui_group_main", "order": 6, "width": 6, "height": 0, "label": "Soil Moisture History", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "Waiting...", "dot": false, "ymin": "0", "ymax": "100", "removeOlder": "3", "removeOlderPoints": "100", "removeOlderUnit": "60", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#2ca02c", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 1130, "y": 600, "wires": [ [] ] }, { "id": "d3167a81e47894f4", "type": "cronplus", "z": "tab1", "name": "Cron Setiap 10 Menit", "outputField": "payload", "timeZone": "", "storeName": "", "commandResponseMsgOutput": "output1", "defaultLocation": "", "defaultLocationType": "default", "outputs": 1, "options": [ { "name": "cron1menit", "topic": "filter/weight", "payloadType": "default", "payload": "", "expressionType": "cron", "expression": "0 */10 * * * *", "location": "", "offset": "0", "solarType": "all", "solarEvents": "sunrise,sunset" } ], "x": 340, "y": 700, "wires": [ [ "1b56edf14dd48a3e" ] ] }, { "id": "1b56edf14dd48a3e", "type": "function", "z": "tab1", "name": "Ambil dan Simpan DB", "func": "var data = flow.get(\"last_sensor_data\");\n\nif (data) {\n msg.topic = `INSERT INTO sensor_data (weight, humidity, soil, timestamp) \n VALUES (${data.weight}, ${data.humidity}, ${data.soil}, datetime('now', 'localtime'))`;\n return msg;\n} else {\n return null;\n}\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 580, "y": 700, "wires": [ [ "5a0b35725a51d4dc" ] ] }, { "id": "94dd8dde94285a13", "type": "function", "z": "tab1", "name": "Simpan data di Flow", "func": "flow.set(\"last_sensor_data\", msg.payload);\nreturn null;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 640, "y": 640, "wires": [ [] ] }, { "id": "bbbaab907e1d9ba1", "type": "ui_dropdown", "z": "tab1", "name": "", "label": "Pilih Data Sensor ", "tooltip": "", "place": "Select option", "group": "a5d4d4b9.0e5d58", "order": 1, "width": 0, "height": 0, "passthru": true, "multiple": false, "options": [ { "label": "Weight", "value": "weight", "type": "str" }, { "label": "Humidity", "value": "humidity", "type": "str" }, { "label": "Soil", "value": "soil", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "x": 330, "y": 780, "wires": [ [ "37d04d285e6d7864" ] ] }, { "id": "f302ee7fa549388d", "type": "ui_button", "z": "tab1", "name": "Ambil Data", "group": "a5d4d4b9.0e5d58", "order": 5, "width": 0, "height": 0, "passthru": false, "label": "Lihat Data 1 Jam Terakhir", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 330, "y": 840, "wires": [ [ "9dea1ff0914d9df1" ] ] }, { "id": "9dea1ff0914d9df1", "type": "function", "z": "tab1", "name": "Ambil Data Sensor", "func": "let sensor = flow.get(\"sensor_selected\") || \"weight\"; // default berat\nmsg.topic = `\n SELECT timestamp, ${sensor} AS value\n FROM sensor_data\n WHERE timestamp >= datetime('now', '-1 hour', 'localtime')\n ORDER BY timestamp ASC\n`;\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 530, "y": 840, "wires": [ [ "b3c205b6f30ee3a1" ] ] }, { "id": "9c2ff6f5414057d6", "type": "function", "z": "tab1", "name": "Ubah Format Chart", "func": "let labels = [];\nlet values = [];\n\nmsg.payload.forEach(row => {\n labels.push(row.timestamp);\n values.push(row.value);\n});\n\nreturn {\n payload: [{\n series: [\"Sensor\"],\n data: [values],\n labels: labels\n }]\n};\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 910, "y": 800, "wires": [ [ "a1e5f5cd0e18215a" ] ] }, { "id": "b3c205b6f30ee3a1", "type": "sqlite", "z": "tab1", "mydb": "sqlite_local", "sqlquery": "msg.topic", "sql": "", "name": "Ambil Data", "x": 730, "y": 840, "wires": [ [ "9c2ff6f5414057d6", "f29389be4bd7f771" ] ] }, { "id": "a1e5f5cd0e18215a", "type": "ui_chart", "z": "tab1", "name": "Chart 1 Jam Terakhir", "group": "a5d4d4b9.0e5d58", "order": 3, "width": "9", "height": "9", "label": "chart", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "3600", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 1140, "y": 800, "wires": [ [] ] }, { "id": "f29389be4bd7f771", "type": "ui_table", "z": "tab1", "group": "a5d4d4b9.0e5d58", "name": "Tabel Data Sensor 1 Jam", "order": 6, "width": "9", "height": "9", "columns": [], "outputs": 0, "cts": false, "x": 930, "y": 880, "wires": [] }, { "id": "1faca8d668b6c953", "type": "mqtt in", "z": "tab1", "name": "MQTT IN", "topic": "iot/sensor", "qos": "2", "datatype": "auto-detect", "broker": "mqtt_broker_local", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 260, "y": 920, "wires": [ [ "43e20a522799a074" ] ] }, { "id": "43e20a522799a074", "type": "function", "z": "tab1", "name": "Logika Otomasi", "func": "let auto = flow.get(\"autoMode\") || \"OFF\";\nlet min = flow.get(\"minThreshold\") || 60;\nlet max = flow.get(\"maxThreshold\") || 90;\nlet humidity = msg.payload.humidity;\n\nif (auto === \"ON\") {\n if (humidity > max || (humidity <= max && humidity >= min)) {\n return {\n payload: {\n kipas: \"ON\",\n ptc: \"ON\"\n }\n };\n } else if (humidity < min) {\n return {\n payload: {\n kipas: \"OFF\",\n ptc: \"OFF\"\n }\n };\n }\n}\n\nreturn null; // Auto OFF → ESP32 logika lokal\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 500, "y": 980, "wires": [ [ "5c1f51279e899535" ] ] }, { "id": "5c1f51279e899535", "type": "mqtt out", "z": "tab1", "name": "MQTT OUT", "topic": "iot/control/relay", "qos": "0", "retain": "true", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "mqtt_broker_local", "x": 730, "y": 920, "wires": [] }, { "id": "d89d2771a3657743", "type": "ui_switch", "z": "tab1", "name": "Auto Mode", "label": "Auto Mode", "tooltip": "", "group": "077642f35af224dd", "order": 1, "width": 0, "height": 0, "passthru": true, "decouple": "false", "topic": "auto", "topicType": "str", "style": "", "onvalue": "ON", "onvalueType": "str", "onicon": "", "oncolor": "", "offvalue": "OFF", "offvalueType": "str", "officon": "", "offcolor": "", "animate": false, "className": "", "x": 270, "y": 980, "wires": [ [ "e6969d414614df2a" ] ] }, { "id": "e6969d414614df2a", "type": "function", "z": "tab1", "name": "Set Auto Mode", "func": "flow.set(\"autoMode\", msg.payload);\n\nlet mode = msg.payload;\n\nif (mode === \"ON\") {\n let min = flow.get(\"minThreshold\") || 60;\n let max = flow.get(\"maxThreshold\") || 90;\n\n return {\n payload: {\n auto: \"ON\",\n min: min,\n max: max\n }\n };\n} else {\n return {\n payload: {\n auto: \"OFF\"\n }\n };\n}\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 520, "y": 1040, "wires": [ [ "c648c08be1a9d688" ] ] }, { "id": "c648c08be1a9d688", "type": "mqtt out", "z": "tab1", "name": "MQTT OUT", "topic": "iot/control/relay", "qos": "0", "retain": "true", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "mqtt_broker_local", "x": 730, "y": 980, "wires": [] }, { "id": "300d03d320030023", "type": "ui_numeric", "z": "tab1", "name": "", "label": "Min Threshold", "tooltip": "", "group": "077642f35af224dd", "order": 2, "width": 0, "height": 0, "wrap": false, "passthru": true, "topic": "min", "topicType": "str", "format": "{{value}}", "min": 0, "max": "100", "step": 1, "className": "", "x": 280, "y": 1040, "wires": [ [ "176a870347715cfa" ] ] }, { "id": "1157d77839be828e", "type": "ui_numeric", "z": "tab1", "name": "Max Threshold", "label": "Max Threshold", "tooltip": "", "group": "077642f35af224dd", "order": 3, "width": 0, "height": 0, "wrap": false, "passthru": true, "topic": "max", "topicType": "str", "format": "{{value}}", "min": 0, "max": "100", "step": 1, "className": "", "x": 280, "y": 1100, "wires": [ [ "bcea903c811a2674" ] ] }, { "id": "176a870347715cfa", "type": "function", "z": "tab1", "name": "MIn Threshold", "func": "flow.set(\"minThreshold\", msg.payload);\nreturn null;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 520, "y": 1100, "wires": [ [] ] }, { "id": "bcea903c811a2674", "type": "function", "z": "tab1", "name": "Max Threshold", "func": "flow.set(\"maxThreshold\", msg.payload);\nreturn null;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 500, "y": 1160, "wires": [ [] ] }, { "id": "eabf72d94dda646b", "type": "ui_form", "z": "tab1", "name": "", "label": "Input Tanggal untuk Cek Tabel Hasil Sensor", "group": "a5d4d4b9.0e5d58", "order": 10, "width": 0, "height": 0, "options": [ { "label": "StartDate", "value": "StartDate", "type": "date", "required": true, "rows": null }, { "label": "EndDate", "value": "EndDate", "type": "date", "required": true, "rows": null } ], "formValue": { "StartDate": "", "EndDate": "" }, "payload": "", "submit": "Load Data", "cancel": "Cancel", "topic": "topic", "topicType": "msg", "splitLayout": "", "className": "", "x": 890, "y": 1080, "wires": [ [ "a924537ad40c9a35" ] ] }, { "id": "a924537ad40c9a35", "type": "function", "z": "tab1", "name": "function 1", "func": "var start = msg.payload.StartDate;\nvar end = msg.payload.EndDate;\n\nmsg.topic = `SELECT ID, timestamp, weight, humidity, soil\n FROM sensor_data\n WHERE timestamp BETWEEN '${start}' AND '${end}'\n ORDER BY timestamp ASC`;\n\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1180, "y": 1080, "wires": [ [ "94ab216e7402fe83" ] ] }, { "id": "94ab216e7402fe83", "type": "sqlite", "z": "tab1", "mydb": "sqlite_local", "sqlquery": "msg.topic", "sql": "", "name": "", "x": 980, "y": 1160, "wires": [ [ "06b6b6875d1ef97f", "009cc5fdaef1059b" ] ] }, { "id": "06b6b6875d1ef97f", "type": "ui_table", "z": "tab1", "group": "a5d4d4b9.0e5d58", "name": "", "order": 11, "width": "18", "height": "9", "columns": [], "outputs": 0, "cts": false, "x": 1310, "y": 1160, "wires": [] }, { "id": "0e1cb4193c91927d", "type": "ui_chart", "z": "tab1", "name": "", "group": "", "order": 12, "width": "9", "height": "9", "label": "Grafik Hasil Sensor", "chartType": "line", "legend": "true", "xformat": "HH:mm", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": "24", "removeOlderPoints": "", "removeOlderUnit": "3600", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 3730, "y": 2780, "wires": [ [] ] }, { "id": "d40f0c435b9f0739", "type": "function", "z": "tab1", "name": "function 2", "func": "let hasil = [];\nlet terakhirJam = null;\n\nfor (let row of msg.payload) {\n let waktu = new Date(row.timestamp);\n let jam = waktu.getHours();\n\n if (jam !== terakhirJam) {\n let t = waktu.getTime();\n hasil.push(\n { topic: \"weight\", payload: row.weight, x: t },\n { topic: \"humidity\", payload: row.humidity, x: t },\n { topic: \"soil\", payload: row.soil, x: t }\n );\n terakhirJam = jam;\n }\n}\n\nreturn [hasil];\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 3740, "y": 2620, "wires": [ [] ] }, { "id": "009cc5fdaef1059b", "type": "debug", "z": "tab1", "name": "debug 1", "active": true, "tosidebar": true, "console": true, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 1010, "y": 1220, "wires": [] }, { "id": "44417d6d5c88b067", "type": "debug", "z": "tab1", "name": "debug 2", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 3900, "y": 2640, "wires": [] }, { "id": "e24142f27563c8f7", "type": "function", "z": "tab1", "name": "function 3", "func": "let hasil = [];\n\nfor (let row of msg.payload) {\n let waktu = new Date(row.timestamp);\n let t = waktu.getTime(); // ubah ke milidetik\n\n hasil.push(\n { topic: \"weight\", payload: row.weight, x: t },\n { topic: \"humidity\", payload: row.humidity, x: t },\n { topic: \"soil\", payload: row.soil, x: t }\n );\n}\n\nreturn [hasil];\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 3740, "y": 2680, "wires": [ [] ] }, { "id": "2274b4168fa225ab", "type": "ui_button", "z": "tab1", "name": "Clear Chart", "group": "", "order": 13, "width": 0, "height": 0, "passthru": false, "label": "Clear Chart", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 3510, "y": 2920, "wires": [ [] ] }, { "id": "f6a49940e4446c3e", "type": "function", "z": "tab1", "name": "function 6", "func": "return [\n { topic: \"weight\", payload: [] },\n { topic: \"humidity\", payload: [] },\n { topic: \"soil\", payload: [] }\n];\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 3520, "y": 2840, "wires": [ [] ] }, { "id": "21536e5562e29615", "type": "function", "z": "tab1", "name": "filter 2jam", "func": "let hasil = [];\nlet terakhirWaktu = null;\nconst intervalMs = 60 * 60 * 1000; // 1 jam = 3600000 ms\n\nfor (let row of msg.payload) {\n let waktu = new Date(row.timestamp);\n if (isNaN(waktu.getTime())) continue;\n\n let t = waktu.getTime();\n\n // Ambil hanya satu data per jam\n if (terakhirWaktu === null || (t - terakhirWaktu) >= intervalMs) {\n hasil.push(\n { topic: \"weight\", payload: row.weight, x: t },\n { topic: \"humidity\", payload: row.humidity, x: t },\n { topic: \"soil\", payload: row.soil, x: t }\n );\n terakhirWaktu = t;\n }\n}\n\nreturn [hasil];\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 3520, "y": 2780, "wires": [ [] ] }, { "id": "c6422797ed00af54", "type": "ui_form", "z": "tab1", "name": "", "label": "Input Waktu untuk Hitung Durasi Nyala Alat", "group": "cfa831704ecb774e", "order": 1, "width": 0, "height": 0, "options": [ { "label": "Waktu Nyala Awal", "value": "start_date", "type": "date", "required": true, "rows": null }, { "label": "Waktu Nyala Akhir", "value": "end_date", "type": "date", "required": true, "rows": null } ], "formValue": { "start_date": "", "end_date": "" }, "payload": "", "submit": "submit", "cancel": "cancel", "topic": "topic", "topicType": "msg", "splitLayout": "", "className": "", "x": 310, "y": 1260, "wires": [ [ "6ab3a01515f3839f" ] ] }, { "id": "6ab3a01515f3839f", "type": "function", "z": "tab1", "name": "query database cari waktu", "func": "let start = msg.payload.start_date;\nlet end = msg.payload.end_date;\n\nmsg.topic = `\n SELECT timestamp, weight FROM sensor_data \n WHERE timestamp BETWEEN '${start} 00:00:00' AND '${end} 23:59:59' \n ORDER BY timestamp ASC;\n`;\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 670, "y": 1260, "wires": [ [ "cf6c9c8b56973d03" ] ] }, { "id": "cf6c9c8b56973d03", "type": "sqlite", "z": "tab1", "mydb": "sqlite_local", "sqlquery": "msg.topic", "sql": "", "name": "", "x": 500, "y": 1340, "wires": [ [ "6d4b3c89d262ec1b" ] ] }, { "id": "6d4b3c89d262ec1b", "type": "function", "z": "tab1", "name": "hitung waktu", "func": "let data = msg.payload;\n\nif (!Array.isArray(data) || data.length < 2) {\n msg.payload = \"Data tidak cukup untuk menghitung durasi pengeringan.\";\n return msg;\n}\n\n// Ganti spasi jadi T agar bisa dibaca Date()\nlet waktu1 = new Date(data[0].timestamp.replace(\" \", \"T\"));\nlet waktu2 = new Date(data[data.length - 1].timestamp.replace(\" \", \"T\"));\n\nif (isNaN(waktu1) || isNaN(waktu2)) {\n msg.payload = \"Format waktu tidak valid dari database.\";\n return msg;\n}\n\nlet selisihMs = Math.abs(waktu2 - waktu1);\nlet jam = Math.floor(selisihMs / (1000 * 60 * 60));\nlet menit = Math.floor((selisihMs % (1000 * 60 * 60)) / (1000 * 60));\n\nmsg.payload = `Durasi Lama Menyala Alat: ${jam} jam ${menit} menit`;\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 2, "initialize": "", "finalize": "", "libs": [], "x": 330, "y": 1420, "wires": [ [ "d7165d9095fce54c" ] ] }, { "id": "d7165d9095fce54c", "type": "ui_text_input", "z": "tab1", "name": "Durasi Alat Menyala", "label": "", "tooltip": "", "group": "cfa831704ecb774e", "order": 2, "width": "14", "height": "2", "passthru": true, "mode": "text", "delay": 300, "topic": "topic", "sendOnBlur": true, "className": "", "topicType": "msg", "x": 600, "y": 1420, "wires": [ [] ] }, { "id": "1d392868362ac611", "type": "ui_form", "z": "tab1", "name": "", "label": "Input Berat Awal dan Akhir", "group": "cfa831704ecb774e", "order": 3, "width": 0, "height": 0, "options": [ { "label": "Berat Awal", "value": "berat_awal", "type": "number", "required": true, "rows": null }, { "label": "Berat Akhir", "value": "berat_akhir", "type": "number", "required": true, "rows": null } ], "formValue": { "berat_awal": "", "berat_akhir": "" }, "payload": "", "submit": "submit", "cancel": "cancel", "topic": "topic", "topicType": "msg", "splitLayout": "", "className": "", "x": 360, "y": 1500, "wires": [ [ "f9c308cbef39eb24" ] ] }, { "id": "f9c308cbef39eb24", "type": "function", "z": "tab1", "name": "query database cari waktu", "func": "let berat_awal = msg.payload.berat_awal;\nlet berat_akhir = msg.payload.berat_akhir;\n\nmsg.topic = `\n SELECT timestamp, weight \n FROM sensor_data \n WHERE weight IN (${berat_awal}, ${berat_akhir})\n ORDER BY timestamp ASC;\n`;\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 670, "y": 1500, "wires": [ [ "36ad9d1719f51b71" ] ] }, { "id": "36ad9d1719f51b71", "type": "sqlite", "z": "tab1", "mydb": "sqlite_local", "sqlquery": "msg.topic", "sql": "", "name": "", "x": 500, "y": 1580, "wires": [ [ "4dc76c949c967437" ] ] }, { "id": "4dc76c949c967437", "type": "function", "z": "tab1", "name": "hitung waktu", "func": "let data = msg.payload;\n\nif (data.length < 2) {\n msg.payload = \"Tidak ditemukan dua titik berat dalam database.\";\n return msg;\n}\n\n// Ubah ke format Date (pastikan format timestamp dari DB: 'YYYY-MM-DD HH:MM:SS')\nlet waktu1 = new Date(data[0].timestamp.replace(\" \", \"T\"));\nlet waktu2 = new Date(data[1].timestamp.replace(\" \", \"T\"));\n\nif (isNaN(waktu1) || isNaN(waktu2)) {\n msg.payload = \"Waktu tidak valid.\";\n return msg;\n}\n\nlet selisihMs = Math.abs(waktu2 - waktu1);\nlet jam = Math.floor(selisihMs / (1000 * 60 * 60));\nlet menit = Math.floor((selisihMs % (1000 * 60 * 60)) / (1000 * 60));\n\nmsg.payload = `Durasi Lama Proses Pengeringan: ${jam} jam ${menit} menit`;\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 2, "initialize": "", "finalize": "", "libs": [], "x": 330, "y": 1660, "wires": [ [ "2ddc72cd58c4a8b2" ] ] }, { "id": "2ddc72cd58c4a8b2", "type": "ui_text_input", "z": "tab1", "name": "Durasi Pengering", "label": "", "tooltip": "", "group": "cfa831704ecb774e", "order": 4, "width": "14", "height": "2", "passthru": true, "mode": "text", "delay": 300, "topic": "topic", "sendOnBlur": true, "className": "", "topicType": "msg", "x": 590, "y": 1660, "wires": [ [] ] }, { "id": "mqtt_broker_local", "type": "mqtt-broker", "name": "Local MQTT", "broker": "localhost", "port": "1883", "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": 4, "keepalive": "60", "cleansession": true, "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "" }, { "id": "sqlite_local", "type": "sqlitedb", "db": "C:\\Users\\Moch Alif Ridho A\\Documents\\sensor_data.db", "mode": "RWC" }, { "id": "ui_group_main", "type": "ui_group", "name": "Sensor Monitor", "tab": "ui_tab_main", "order": 1, "disp": true, "width": "18", "collapse": false, "className": "" }, { "id": "a5d4d4b9.0e5d58", "type": "ui_group", "name": "Filter Data Sensor", "tab": "c75362e0.9e6a38", "order": 1, "disp": true, "width": "18", "collapse": false, "className": "" }, { "id": "077642f35af224dd", "type": "ui_group", "name": "Kontrol Relay", "tab": "c94c9feca08755da", "order": 1, "disp": true, "width": "18", "collapse": false, "className": "" }, { "id": "cfa831704ecb774e", "type": "ui_group", "name": "Hitung Lama Pengeringan", "tab": "2e2f751f9fd061a0", "order": 1, "disp": true, "width": "18", "collapse": false, "className": "" }, { "id": "ui_tab_main", "type": "ui_tab", "name": "Dashboard Monitoring", "icon": "dashboard", "order": 2, "disabled": false, "hidden": false }, { "id": "c75362e0.9e6a38", "type": "ui_tab", "name": "Dashboard Database ", "icon": "dashboard", "order": 3, "disabled": false, "hidden": false }, { "id": "c94c9feca08755da", "type": "ui_tab", "name": "Otomasi Relay", "icon": "dashboard", "order": 5, "disabled": false, "hidden": false }, { "id": "2e2f751f9fd061a0", "type": "ui_tab", "name": "Hitung Durasi Proses Pengeringan", "icon": "dashboard", "order": 5, "disabled": false, "hidden": false } ]