[ { "id": "dd166f1fb1414356", "type": "tab", "label": "Dashboard AC", "disabled": false, "info": "" }, { "id": "45a2b398537a5711", "type": "ui_switch", "z": "dd166f1fb1414356", "name": "Power", "label": "Power", "tooltip": "", "group": "8bd4a1c79f424e45", "order": 1, "width": 3, "height": 1, "passthru": true, "decouple": "false", "topic": "ac/power", "topicType": "str", "style": "", "onvalue": "on", "onvalueType": "str", "onicon": "", "oncolor": "", "offvalue": "off", "offvalueType": "str", "officon": "", "offcolor": "", "animate": true, "className": "", "x": 210, "y": 60, "wires": [ [ "e26985a773758b5b", "fed55988a21fe62d" ] ] }, { "id": "e09b28a2caeb4107", "type": "ui_dropdown", "z": "dd166f1fb1414356", "name": "Swing", "label": "Swing", "tooltip": "", "place": "Select option", "group": "8bd4a1c79f424e45", "order": 4, "width": 6, "height": 1, "passthru": true, "multiple": false, "options": [ { "label": "Swing ON", "value": "on", "type": "str" }, { "label": "Swing OFF", "value": "off", "type": "str" } ], "topic": "ac/swing", "topicType": "str", "className": "", "x": 210, "y": 180, "wires": [ [ "e26985a773758b5b", "fed55988a21fe62d" ] ] }, { "id": "e26985a773758b5b", "type": "mqtt out", "z": "dd166f1fb1414356", "name": "MQTT KONTROL AC", "topic": "", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "9fbca1958dadbcd5", "x": 480, "y": 60, "wires": [] }, { "id": "f7fd6d577815fc4e", "type": "mqtt in", "z": "dd166f1fb1414356", "name": "", "topic": "sensor/dht22", "qos": "2", "datatype": "json", "broker": "9fbca1958dadbcd5", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 1070, "y": 380, "wires": [ [ "4daa938f22269682", "e834877d3f06063a", "1c4be2044b325aeb" ] ] }, { "id": "e834877d3f06063a", "type": "function", "z": "dd166f1fb1414356", "name": "function 2", "func": "var suhu = { payload: msg.payload.suhu };\nvar kelembapan = { payload: msg.payload.kelembapan };\nreturn [\n { payload: msg.payload.suhu },\n { payload: msg.payload.kelembapan }\n];\n\nlet data = JSON.parse(msg.payload);\nmsg.payload = {\n suhu: data.temperature,\n kelembapan: data.humidity,\n waktu: new Date().toLocaleString()\n};\nreturn msg;", "outputs": 2, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1280, "y": 380, "wires": [ [ "6bd53add46a3c498" ], [ "00983c16aeed32b2" ] ] }, { "id": "6bd53add46a3c498", "type": "ui_gauge", "z": "dd166f1fb1414356", "name": "", "group": "565ebc18e3d1af6f", "order": 2, "width": 6, "height": 4, "gtype": "gage", "title": "Suhu", "label": "°C", "format": "{{value}} °C", "min": 0, "max": "50", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 1450, "y": 360, "wires": [] }, { "id": "00983c16aeed32b2", "type": "ui_gauge", "z": "dd166f1fb1414356", "name": "", "group": "565ebc18e3d1af6f", "order": 1, "width": 0, "height": 0, "gtype": "gage", "title": "Kelembapan", "label": "%", "format": "{{value}} %", "min": 0, "max": "100", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 1470, "y": 400, "wires": [] }, { "id": "cf15d38ecdd8677a", "type": "ui_dropdown", "z": "dd166f1fb1414356", "name": "Fan Speed", "label": "Fan Speed", "tooltip": "", "place": "Select option", "group": "8bd4a1c79f424e45", "order": 5, "width": 6, "height": 1, "passthru": true, "multiple": false, "options": [ { "label": "Auto", "value": "auto", "type": "str" }, { "label": "Low", "value": "low", "type": "str" }, { "label": "Medium", "value": "medium", "type": "str" }, { "label": "High", "value": "high", "type": "str" } ], "topic": "ac/fan", "topicType": "str", "className": "", "x": 190, "y": 240, "wires": [ [ "e26985a773758b5b", "fed55988a21fe62d" ] ] }, { "id": "789737a05200ad73", "type": "ui_button", "z": "dd166f1fb1414356", "name": "Reset WiFi ESP32", "group": "565ebc18e3d1af6f", "order": 6, "width": 6, "height": 1, "passthru": false, "label": "Reset WiFi ESP32", "tooltip": "Klik untuk reset koneksi WiFi", "color": "", "bgcolor": "#ff6666", "className": "", "icon": "fa-refresh", "payload": "reset", "payloadType": "str", "topic": "ac/resetwifi", "topicType": "str", "x": 190, "y": 340, "wires": [ [ "456c3c5f815e9edf" ] ] }, { "id": "f043c813369ccfc7", "type": "ui_slider", "z": "dd166f1fb1414356", "name": "Suhu", "label": "Temperature", "tooltip": "", "group": "8bd4a1c79f424e45", "order": 3, "width": 6, "height": 1, "passthru": true, "outs": "end", "topic": "ac/temp", "topicType": "str", "min": 18, "max": 30, "step": 1, "className": "", "x": 210, "y": 120, "wires": [ [ "e26985a773758b5b", "fed55988a21fe62d" ] ] }, { "id": "fed55988a21fe62d", "type": "function", "z": "dd166f1fb1414356", "name": "Send to Firebase and UI Table", "func": "let topic = msg.topic\nlet value = msg.payload;\nlet timestamp = Date.now();\nlet waktu = new Date().toLocaleString(\"id-ID\");\n\n// Ambil status terakhir AC\nlet lastState = flow.get(\"statusAC\") || {\n power: null,\n temp: null,\n swing: null,\n fan: null\n};\n\n// Perbarui status sesuai topik\nif (topic === \"ac/power\") {\n lastState.power = value;\n} else if (topic === \"ac/temp\") {\n lastState.temp = value;\n} else if (topic === \"ac/swing\") {\n lastState.swing = value;\n} else if (topic === \"ac/fan\") {\n lastState.fan = value;\n} else if (topic === \"reset\") {\n // Reset semua\n flow.set(\"statusAC\", null);\n flow.set(\"riwayatAC\", []);\n\n let msgFirebase = {\n payload: null,\n topic: \"ac/power\"\n };\n let msgTable = {\n payload: []\n };\n return [msgFirebase, msgTable];\n} else {\n return [null, null]; // Topik tidak dikenal\n}\n\n// Simpan ulang status terakhir\nflow.set(\"statusAC\", lastState);\n\n// Buat entry riwayat lengkap\nlet dataBaru = {\n timestamp,\n waktu,\n power: lastState.power,\n temp: lastState.temp,\n swing: lastState.swing,\n fan: lastState.fan\n};\n\n// Simpan ke array riwayat\nlet history = flow.get(\"riwayatAC\") || [];\nhistory.push(dataBaru);\n\n\nflow.set(\"riwayatAC\", history);\n\nlet msgFirebase = {\n payload: dataBaru,\n topic: `ac_${timestamp}` // untuk ID dokumen unik\n};\n\n// Kirim ke Firebase\n// let msgFirebase = {\n// payload: {\n// [timestamp]: dataBaru\n// },\n// topic: \"ac/power\"\n// };\n\n// Kirim ke UI Table\n// riwayatAC:\n// [\"on\", \"off\"]\n\nlet msgTable = {\n payload: history\n};\n\nreturn [msgFirebase, msgTable];", "outputs": 2, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 550, "y": 180, "wires": [ [ "afcf08f2c2555abf" ], [ "9776cfb20df1c2e3" ] ] }, { "id": "f1f65fb261894e0d", "type": "ui_button", "z": "dd166f1fb1414356", "name": "Button Reset", "group": "f893e31c484e44b1", "order": 2, "width": 0, "height": 0, "passthru": false, "label": "Reset History", "tooltip": "", "color": "", "bgcolor": "#ff6666", "className": "", "icon": "delete", "payload": "", "payloadType": "str", "topic": "reset", "topicType": "str", "x": 450, "y": 280, "wires": [ [ "fed55988a21fe62d" ] ] }, { "id": "9776cfb20df1c2e3", "type": "ui_template", "z": "dd166f1fb1414356", "group": "f893e31c484e44b1", "name": "Table Riwayat AC", "order": 1, "width": "6", "height": "7", "format": "
\n \n \n \n \n \n \n \n \n \n \n \n \n
WaktuPowerTempSwingFan
\n
\n\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": false, "templateScope": "local", "className": "", "x": 850, "y": 180, "wires": [ [] ] }, { "id": "456c3c5f815e9edf", "type": "mqtt out", "z": "dd166f1fb1414356", "name": "MQTT RESET WIFI", "topic": "ac/resetwifi", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "9fbca1958dadbcd5", "x": 470, "y": 340, "wires": [] }, { "id": "77f6c3bcdb24cb4b", "type": "function", "z": "dd166f1fb1414356", "name": "Builder Cron dari Form", "func": "let waktu = msg.payload.time;\nlet aksi = msg.payload.action;\nlet hari = msg.payload.hari || \"\";\nlet tanggal = msg.payload.tanggal || \"\";\n\n// Deteksi dan ubah waktu ISO (jika input adalah string format ISO)\nif (typeof waktu === 'string' && waktu.includes(\"T\")) {\n try {\n let t = new Date(waktu);\n if (isNaN(t.getTime())) throw \"Invalid time\";\n let jam = t.getHours().toString().padStart(2, \"0\");\n let menit = t.getMinutes().toString().padStart(2, \"0\");\n waktu = `${jam}:${menit}`;\n } catch (e) {\n node.error(\"⛔ Format waktu ISO tidak valid.\");\n return null;\n }\n}\n\n// Pastikan waktu sudah dalam format HH:MM\nif (!/^\\d{2}:\\d{2}$/.test(waktu)) {\n node.error(\"⛔ Format waktu tidak valid. Harus HH:MM.\");\n return null;\n}\n\nlet [jam, menit] = waktu.split(\":\").map(Number);\nif (isNaN(jam) || isNaN(menit)) {\n node.error(\"Jam atau menit bukan angka.\");\n return null;\n}\n\nlet id = `jadwal_${jam}_${menit}_${aksi}_${Date.now()}`;\n\nlet dayMap = {\n \"Minggu\": 0,\n \"Senin\": 1,\n \"Selasa\": 2,\n \"Rabu\": 3,\n \"Kamis\": 4,\n \"Jumat\": 5,\n \"Sabtu\": 6\n};\n\nlet expression = \"\";\n\nif (hari !== \"\") {\n let hariList = hari.split(\",\").map(h => dayMap[h.trim()]);\n expression = `0 ${menit} ${jam} * * ${hariList.join(\",\")}`;\n} else if (tanggal !== \"\") {\n let dt = new Date(tanggal);\n if (isNaN(dt)) {\n node.error(\"Tanggal tidak valid.\");\n return null;\n }\n let day = dt.getDate();\n let month = dt.getMonth() + 1;\n let year = dt.getFullYear();\n expression = `0 ${menit} ${jam} ${day} ${month} * ${year}`;\n} else {\n expression = `0 ${menit} ${jam} * * *`;\n}\n\nmsg.payload = {\ncommand: \"add\",\nname: id,\n expression: expression,\n payload: {\n topic: \"ac/power\",\n payload: aksi // <- pastikan ini ada\n },\n payloadType: \"default\",\n timezone: \"Asia/Jakarta\"\n};\n\n// Simpan data jadwal ke dalam msg untuk disimpan ke tabel UI\nmsg.jadwalInfo = {\n id: id,\n waktu: waktu,\n aksi: aksi,\n hari: hari,\n tanggal: tanggal,\n expression: expression\n};\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 440, "y": 700, "wires": [ [ "5de18712a68b9fad", "10101b10536635de" ] ] }, { "id": "dc609343abd6df63", "type": "mqtt out", "z": "dd166f1fb1414356", "name": "MQTT KONTROL AC", "topic": "ac/power", "qos": "0", "retain": "false", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "9fbca1958dadbcd5", "x": 1100, "y": 700, "wires": [] }, { "id": "549d0a5c93b2f529", "type": "function", "z": "dd166f1fb1414356", "name": "function 1", "func": "if (msg.payload && msg.payload.config && msg.payload.config.payload) {\n msg.topic = msg.payload.config.payload.topic;\n msg.payload = msg.payload.config.payload.payload;\n return msg;\n// } else {\n// node.warn(\"⛔ Payload cron tidak sesuai.\");\n// return null;\n}", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 880, "y": 700, "wires": [ [ "dc609343abd6df63", "fed55988a21fe62d" ] ] }, { "id": "56ae79c457a7ed78", "type": "ui_template", "z": "dd166f1fb1414356", "group": "37291ecad70d0e70", "name": "Form Jadwal AC", "order": 1, "width": 6, "height": "10", "format": "
\n

Buat Jadwal Kontrol AC

\n\n
\n
\n \n
\n\n
\n
\n \n
\n\n
\n
\n \n
\n\n
\n
\n \n
\n\n
\n \n ➕ Tambah Jadwal\n \n
\n
\n\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "local", "className": "", "x": 180, "y": 700, "wires": [ [ "77f6c3bcdb24cb4b" ] ] }, { "id": "018cdc13cbbcfb25", "type": "ui_template", "z": "dd166f1fb1414356", "group": "9ed1eb381b037864", "name": "", "order": 1, "width": "16", "height": "10", "format": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Jadwal AC
IDWaktuHariStatusTanggalExpressionStateDeletePauseResume
{{item.id}}{{item.waktu}}{{item.hari}}{{item.aksi}}{{item.tanggal}}{{item.expression}}{{item.state}}\n \n \n \n \n \n \n \n \n \n \n \n
", "storeOutMessages": false, "fwdInMessages": false, "resendOnRefresh": false, "templateScope": "local", "className": "", "x": 840, "y": 820, "wires": [ [ "fde5a22acf4ec433" ] ] }, { "id": "7db5bda8a2122f27", "type": "function", "z": "dd166f1fb1414356", "name": "Make table data", "func": "let raw = msg.payload.result || [];\nif(!msg.payload || !msg.payload.result || !msg.payload.result.length){\n msg.payload = [];\n return msg;\n}\n\nmsg.payload = msg.payload.result;\n\nlet dayMapReverse = {\n \"0\": \"Minggu\",\n \"1\": \"Senin\",\n \"2\": \"Selasa\",\n \"3\": \"Rabu\",\n \"4\": \"Kamis\",\n \"5\": \"Jumat\",\n \"6\": \"Sabtu\"\n};\n\nlet jadwals = raw.map(item => {\n let expr = item.config.expression.split(\" \");\n let menit = expr[1]?.padStart(2, '0') || \"00\";\n let jam = expr[2]?.padStart(2, '0') || \"00\";\n let tanggal = \"-\";\n let hari = \"-\";\n let tahun = expr[6] !== undefined ? expr[6] : \"\";\n\n if (expr[3] !== \"*\" && expr[4] !== \"*\") {\n // By tanggal\n tanggal = `${expr[3].padStart(2, '0')}/${expr[4].padStart(2, '0')}`;\n if (tahun) tanggal += `/${tahun}`;\n } else if (expr[5] !== \"*\") {\n // By hari (misal: 1,3,5)\n hari = expr[5]\n .split(\",\")\n .map(h => dayMapReverse[h.trim()] || h)\n .join(\", \");\n }\n\n return {\n id: item.config.name,\n waktu: `${jam}:${menit}`,\n hari: hari,\n aksi: item.config.payload?.payload || \"-\", // ambil dari payload.payload\n tanggal: tanggal,\n expression: item.config.expression,\n state: item.status?.isRunning ? \"Running\" : \"Paused\"\n };\n});\n\nmsg.payload = jadwals;\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 660, "y": 820, "wires": [ [ "018cdc13cbbcfb25" ] ] }, { "id": "10101b10536635de", "type": "ui_button", "z": "dd166f1fb1414356", "name": "Refresh", "group": "9ed1eb381b037864", "order": 6, "width": 0, "height": 0, "passthru": true, "label": "", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "fa-refresh", "payload": "{\"command\":\"list-all\"}", "payloadType": "json", "topic": "", "topicType": "str", "x": 540, "y": 620, "wires": [ [ "5de18712a68b9fad" ] ] }, { "id": "fde5a22acf4ec433", "type": "link out", "z": "dd166f1fb1414356", "name": "link out 1", "mode": "link", "links": [ "a7a4af9974c0b0b7" ], "x": 955, "y": 820, "wires": [] }, { "id": "a7a4af9974c0b0b7", "type": "link in", "z": "dd166f1fb1414356", "name": "link in 1", "links": [ "fde5a22acf4ec433" ], "x": 715, "y": 620, "wires": [ [ "5de18712a68b9fad" ] ] }, { "id": "5de18712a68b9fad", "type": "cronplus", "z": "dd166f1fb1414356", "name": "cron plus", "outputField": "payload", "timeZone": "Asia/Jakarta", "storeName": "", "commandResponseMsgOutput": "output1", "defaultLocation": "", "defaultLocationType": "default", "outputs": 1, "options": [], "x": 680, "y": 700, "wires": [ [ "549d0a5c93b2f529", "7db5bda8a2122f27" ] ] }, { "id": "0d32caa7f8235bfc", "type": "function", "z": "dd166f1fb1414356", "name": "function 3", "func": "let timestamp = new Date().getTime().toString();\n\nmsg.payload = {\n suhu: 18,\n kelembapan: 70,\n waktu: new Date().toLocaleString(\"id-ID\")\n};\n\nmsg.document = timestamp;\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1220, "y": 240, "wires": [ [ "6e5888c6866aa74e" ] ] }, { "id": "6e5888c6866aa74e", "type": "Firestore out", "z": "dd166f1fb1414356", "name": "Kirim ke Firestore", "collection": "sensor_dht22", "document": "msg.document", "operation": "set", "admin": "6a37fe7abbfec3e2", "eject": false, "x": 1450, "y": 240, "wires": [ [] ] }, { "id": "c0b3097096ea1936", "type": "inject", "z": "dd166f1fb1414356", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 1060, "y": 240, "wires": [ [ "0d32caa7f8235bfc" ] ] }, { "id": "afcf08f2c2555abf", "type": "Firestore out", "z": "dd166f1fb1414356", "name": "Kirim ke Firestore", "collection": "KONTROL AC", "document": "{{topic}}", "operation": "set", "admin": "6a37fe7abbfec3e2", "eject": false, "x": 830, "y": 80, "wires": [ [] ] }, { "id": "4061d92b9545a878", "type": "Firestore out", "z": "dd166f1fb1414356", "name": "Kirim ke Firestore", "collection": "Sensor DHT22", "document": "{{topic}}", "operation": "set", "admin": "6a37fe7abbfec3e2", "eject": false, "x": 1450, "y": 460, "wires": [ [] ] }, { "id": "4daa938f22269682", "type": "ui_template", "z": "dd166f1fb1414356", "group": "565ebc18e3d1af6f", "name": "", "order": 3, "width": 0, "height": 0, "format": "
\n Suhu {{msg.payload.status === 'sesuai' ? 'Sudah Sesuai ✅' : 'Belum Sesuai ⚠️'}}\n
", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "local", "className": "", "x": 1160, "y": 540, "wires": [ [] ] }, { "id": "1c4be2044b325aeb", "type": "function", "z": "dd166f1fb1414356", "name": "function 4", "func": "let timestamp = new Date().toISOString();\n\n// Ambil data sesuai field sebenarnya\nlet suhu = msg.payload.suhu;\nlet kelembapan = msg.payload.kelembapan;\nlet status = msg.payload.status;\n\nif (suhu === undefined || kelembapan === undefined) {\n node.error(\"Data suhu atau kelembapan tidak ditemukan\", msg);\n return null;\n}\n\n// Format untuk Firestore\nmsg.topic = timestamp;\nmsg.payload = {\n suhu: suhu,\n kelembapan: kelembapan,\n status: status,\n waktu: new Date().toLocaleString(\"id-ID\")\n};\n\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1260, "y": 460, "wires": [ [ "4061d92b9545a878" ] ] }, { "id": "75da73cb7683e5d2", "type": "ui_spacer", "z": "dd166f1fb1414356", "name": "spacer", "group": "8bd4a1c79f424e45", "order": 2, "width": 3, "height": 1 }, { "id": "8bd4a1c79f424e45", "type": "ui_group", "name": "KONTROL AC", "tab": "6d66f5b86ad8ba34", "order": 1, "disp": true, "width": 6, "collapse": false, "className": "" }, { "id": "9fbca1958dadbcd5", "type": "mqtt-broker", "name": "HiveMQ Private", "broker": "", "port": "", "tls": "", "clientid": "", "autoConnect": true, "usetls": true, "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": "565ebc18e3d1af6f", "type": "ui_group", "name": "SUHU DAN KELEMBAPAN", "tab": "6d66f5b86ad8ba34", "order": 2, "disp": true, "width": 6, "collapse": false, "className": "" }, { "id": "f893e31c484e44b1", "type": "ui_group", "name": "History", "tab": "6d66f5b86ad8ba34", "order": 3, "disp": true, "width": 6, "collapse": false, "className": "" }, { "id": "37291ecad70d0e70", "type": "ui_group", "name": "ATUR JADWAL", "tab": "39df619663f80ba7", "order": 1, "disp": true, "width": 6, "collapse": false, "className": "" }, { "id": "9ed1eb381b037864", "type": "ui_group", "name": "DAFTAR JADWAL", "tab": "39df619663f80ba7", "order": 2, "disp": true, "width": "16", "collapse": false, "className": "" }, { "id": "6a37fe7abbfec3e2", "type": "firebase admin", "name": "FirebaseAC" }, { "id": "6d66f5b86ad8ba34", "type": "ui_tab", "name": "CONTROL DAN MONITORING AC", "icon": "dashboard", "order": 1, "disabled": false, "hidden": false }, { "id": "39df619663f80ba7", "type": "ui_tab", "name": "PENJADWALAN", "icon": "dashboard", "disabled": false, "hidden": false } ]