tuyaDAEMON IOT development framework

"TuyaDAEMON's goal is not to control some Tuya devices but to integrate the entire Tuya ecosystem in node-red, creating a new level of abstraction that includes both Tuya-cloud and all possible extensions: custom devices and logic of extended automation."

Why tuyaDAEMON ?

tuyaDEAMON is a rich framework for IoT, offering to the user-programmer a powerful event processor, the CORE, and many ways to implement their own projects:

  1. all devices: smart-tuya, virtual, custom, SW-only,... are handled in the same uniform way by tuyaDEAMON.core.
  2. TuyaDAEMON uses MQTT and TRIGGER channels to talk to smart Tuya devices. The user is guaranteed that in any case all Tuya devices will be integrated.
  3. users can build a chain from the existing tasks to get the required complex behavior (meta-programming).
  4. users can add new functionalities, i.e. new tasks, building more js SW only devices, to cover the sector of interest (_system is an example).
  5. User can build new apps and UI, using node-red (see the watering_sys UI), or the REST interface (see the PHP app tuyaDAEMON.toolkit that uses the REST to test new devices), or accessing the DB.
  6. users can design and build new devices from existing ones, in OO style (see example: watering_sys)
  7. users can add an HW not-tuya device, with a simple node-red interface flow (example: PM_detector)
  8. This project contains a growing collection of known tuya smart devices, and any user can easily explore the capabilities of new devices using tuyaDEAMON.toolkit.

Main repository: https://github.com/msillano/tuyaDAEMON

WIKI: https://github.com/msillano/tuyaDAEMON/wiki

This flow is an example: 'PM_detector' custom device.

[{"id":"ee953c15.ada29","type":"subflow","name":"static retry","info":"## 'static retry' for tuya devices ##\r\n\r\nHandles tuya device status changes.\r\n\r\nProcesses the status of a real device , if the connection status changes:\r\n   - Creates a log msg (deviceId, \"_connected\", true/false) for standard processing\r\n \r\nIMPORTANT: set the ** deviceID ** parameter with the correct ID value.\r\n\r\nsee: [wiki](https://github.com/msillano/tuyaDAEMON/wiki/ver.-2.0--milestones#devices-control).","category":"","in":[{"x":80,"y":80,"wires":[{"id":"6160136.f5f99ec"}]}],"out":[{"x":440,"y":80,"wires":[{"id":"6160136.f5f99ec","port":0}]}],"env":[{"name":"deviceId","type":"str","value":""}],"color":"#DDAA99","inputLabels":["in staus"],"outputLabels":["to log"],"icon":"font-awesome/fa-rss-square"},{"id":"6160136.f5f99ec","type":"function","z":"ee953c15.ada29","name":"connected message - static","func":"// to set global.tuyasyatus.xxxx._connected \n// creates a response for the dp \"_connected\"\n// store value and filter events\n\nvar CONN = \"_connected\";  //  tuyadaemon internal define\n// see also tuyaDAEMON.\"connection check\" function\n// local function\nfunction _sendReal(idevice,  value){\n// builds a fake OUT message \nvar newMsg = { \"payload\":{\n      \"deviceId\": idevice,\n      \"data\": {\n          \"dps\":{\n            [CONN]:value\n          }}}};\nreturn newMsg;  \n}\n\n// user set parameter device id\nvar deviceId = env.get(\"deviceId\");\nvar nowconnected =  (msg.status.text == \"connected\");        // can change\n// filering \nlet old = flow.get(deviceId+\"_con\");\nif (old === nowconnected) return null;\nflow.set(deviceId+\"_con\", nowconnected);\n\nreturn(_sendReal(deviceId, nowconnected));\n\n\n\n\n\n","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// set the default status _connected to null in tuyastatus\n// creates tuyastatus entry to null.\nvar alldevices = global.get(\"alldevices\");\nif (alldevices === undefined) return null;\nvar deviceId = env.get(\"deviceId\");\n// only for REAL devices\nvar d = alldevices.real.find(device => device.id === deviceId);\nif (d === undefined) return null;\n//\nflow.set(deviceId+\"_con\", false);\nvar tuyastatus = global.get(\"tuyastatus\");\n    if (tuyastatus === undefined)\n        tuyastatus = {};\n    var dev = (d.name?d.name:(d.cid?d.cid:d.id));\n    if (tuyastatus[dev] === undefined) {\n        tuyastatus[dev] = {_connected:false};\n        global.set(\"tuyastatus\", tuyastatus);\n    }\n    ","finalize":"","x":260,"y":80,"wires":[[]]},{"id":"ee008879.f5adc8","type":"tab","label":"custom.PM detector","disabled":false,"info":"**custom device PM detector\n\nFlow to use a USB-serial PM detector with tuyaDAEMON. Example of use of not-tuya HW devices.\n\nnote: You can fire some tests, used for debug and as use example. You can also delete it.\n\nsee: https://github.com/msillano/tuyaDAEMON/wiki/custom-device-'PM-detector':-case-study"},{"id":"e37f5692.37de78","type":"serial in","z":"ee008879.f5adc8","name":"*COM19","serial":"45a4b402.6f7e4c","x":820,"y":380,"wires":[["170c898c.35c906","3787ab01.6ee494"]]},{"id":"28f4d04b.ed1b7","type":"serial out","z":"ee008879.f5adc8","name":"*COM19","serial":"45a4b402.6f7e4c","x":820,"y":300,"wires":[]},{"id":"fa5e5a63.b11488","type":"function","z":"ee008879.f5adc8","name":"pick and execute","func":"// converts the msg (dp/set) to serial format required by PM2.5USB\nfunction formatI(n) { // local function\n    return (n > 99 ? \"\" + n : (n > 9 ? \"0\" + n : \"00\" + n));\n}\n\n// returns now(), formatted for mySQL, never fails\nfunction _mySQLdatetime() {\n    //datetime format for mySQL: '2020-12-18 20:45:02'\n    // using 'sv' for date format (but local timezone)\n    return (new Date().toLocaleString('sv'));\n}\n\nfunction isNone(value) { // for resetCycle\n    if (value) {\n        if (typeof(value) === \"string\") {\n            if (value.toUpperCase() === 'OFF')\n                return true;\n            if (value.toUpperCase() === 'NO')\n                return true;\n            if (value.toUpperCase() === 'NONE')\n                return true;\n        }\n        return false;\n    }\n    return true;\n}\n\n// test: idDev SET command?\nfunction _isSetCommand(msx) {\n    return ((msx.payload.property !== undefined) && (msx.payload.value !== undefined));\n}\n\n// test: idDev GET command?\nfunction _isGetCommand(msx) {\n    return ((msx.payload.property !== undefined) && (msx.payload.value === undefined));\n}\n\nconst FASTDP = \"_refreshCycle\"; // set: 'off'| time [s]\nconst REFDP = \"_refresh\"; // set: any (trigger)\nconst DEVIDPM1 = \"_PM1_USB\";\n//base message to log\nvar newMsg = {\n    payload: {\n        deviceId: DEVIDPM1,\n        data: {\n            dps: {}\n        }\n    }\n};\n\n// ---------------  main\n// fast exit\nif (msg.to !== DEVIDPM1)\n    return ([null]);\n\n// from property/value to serial for PM detector\nif (_isSetCommand(msg)) { // test: is SET\n    // node.warn([\"INPUT cmd\", msg ]);\n    var data = \"\";\n    switch (msg.infodp) {\n    case REFDP:\n        data = {\n            fun: \"05\",\n            flag: flow.set(\"pm_1\")\n        }\n        msg.payload.value = \"done\";\n        break;\n    case \"1\":\n        flow.set(\"pm_\" + msg.infodp, msg.payload.value);\n        data = {\n            fun: \"01\",\n            sendtime: formatI(msg.payload.value)\n        }\n        break;\n    case FASTDP:\n        if (isNone(msg.payload.value)) {\n            data = {\n                fun: \"05\",\n                flag: \"0\"\n            };\n            flow.set(\"pm_5\", \"0\");\n            msg.payload.value = \"stop\";\n            break;\n        }\n        flow.set(\"pm_1\", msg.payload.value);\n        data = {\n            fun: \"01\",\n            sendtime: formatI(msg.payload.value)\n        };\n        msg.payload = JSON.stringify(data);\n        node.send([msg]);\n        flow.set(\"pm_5\", \"1\");\n        data = {\n                fun: \"05\",\n                flag: \"1\"\n            };\n        break;\n    case \"2\":\n        flow.set(\"pm_\" + msg.payload.dp, msg.payload.value);\n        data = {\n            fun: \"02\",\n            storetime: formatI(msg.payload.value)\n        }\n        break;\n    case \"3\":\n        if ((msg.payload.value === undefined) || (msg.payload.value == false))\n            msg.payload.value = _mySQLdatetime().substring(2);\n        data = {\n            fun: \"03\",\n            clock: msg.payload.value\n        }\n        break;\n    case \"4\":\n        data = {\n            fun: \"04\"\n        }\n        msg.payload.value = \"done\"; // is trigger\n        break;\n    case \"5\":\n        msg.payload.value = msg.payload.value ? \"1\" : \"0\";\n        flow.set(\"pm_\" + msg.infodp, msg.payload.value);\n        data = {\n            fun: \"05\",\n            flag: (msg.payload.value ? \"1\" : \"0\")\n        }\n        break;\n    case \"6\":\n        data = {\n            fun: \"06\"\n        }\n        msg.payload.value = \"done\"; // is trigger\n        break;\n    default:\n        node.warn(\"The SET command '\" + msg.payload.property + \"'(\" + msg.infodp + \") => '\" + msg.payload.value + \"' is not allowed for PM detector device '\");\n        return;\n    }\n    newMsg.payload.data.dps[msg.infodp] = msg.payload.value;\n    msg.payload = JSON.stringify(data);\n    return [msg, newMsg];\n}\n\nif (_isGetCommand(msg)) { // test: is GET\n    switch (msg.infodp) {\n    case \"1\":\n    case \"2\":\n    case \"3\":\n    case \"5\":\n        newMsg.payload.data.dps[msg.infodp] = flow.get(\"pm_\" + msg.infodp);\n        return [null, newMsg];\n    case \"80\":\n        msg.payload = '{\"fun\":\"80\"}';\n        return [msg];\n    case FASTDP:\n        newMsg.payload.data.dps[msg.infodp] = flow.get(\"pm_1\");\n        return [null, newMsg];\n\n    }\n    node.warn(\"The GET command '\" + msg.payload.property + \"'(\" + msg.infodp + \") is not allowed for PM detector device '\");\n    return;\n}\n","outputs":2,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\nflow.set(\"skipdata\", false);\n// set as connected\n// --------------- locals\nfunction _callJSONMethod(obj, fname, a, b, c, d) {\n    var wrap = s => \"{ return \" + obj[fname] + \" };\" //return the block having function expression\n    var func = new Function(wrap(obj[fname]));       // ignore ⚠\n    return func.call(null).call(obj, a, b, c, d);    //invoke the function using arguments\n}\n\n// ---------------- main\nvar PMd = \"_PM1_USB\"; // can have friendly name\n// stuff to access data\nvar alld = global.get(\"alldevices\");\nif (alld === undefined)\n    return null;\n// find object dp\nvar oDev = _callJSONMethod(alld, \"__getODev\", PMd, \"fake\");\nvar pmDevName = _callJSONMethod(alld, \"__getDevName\", oDev);\n//\ntuyastatus = global.get(\"tuyastatus\");\nif (tuyastatus === undefined)\n    tuyastatus = {};\nif (tuyastatus[pmDevName] === undefined)\n    tuyastatus[pmDevName] = {};\n// init\ntuyastatus[pmDevName][\"_connected\"] = true;\nglobal.set(\"tuyastatus\", tuyastatus);\n","finalize":"","x":170,"y":320,"wires":[["8ce5e629.3c5ef8"],["e914f703.e257b8"]]},{"id":"170c898c.35c906","type":"function","z":"ee008879.f5adc8","name":"RT data processing","func":"// convert serial data from PM2.5 to usual format\n// payload: object\n//     deviceId: \"123455aa196ae5******\"\n//     data: object\n//        t: 1609234903\n//        dps: object\n//           102: 1020\n//\nvar answ = {\n    deviceId: \"_PM1_USB\",\n    data : {\n          t : Math.floor(Date.now() / 1000),\n          dps : {\n              \n          }\n       }\n    }\n\nvar data = JSON.parse(msg.payload.replace(\",}\",\"}\")); \n\nswitch (data.res) {\n        case \"1\":\n        case \"2\":\n        case \"3\":\n              answ.data.dps[data.res] = flow.get(\"pm_\"+data.res);\n              break;\n        case \"5\":\n              answ.data.dps[data.res] =  \" done \";\n              break;\n        case \"4\":\n              if (flow.get(\"skipdata\")) return;\n              if (Number(data[\"cpm1.0\"]) == 0) return;  // bad data\n              answ.data.dps = {\n                 200: Number(data[\"cpm1.0\"]),\n                 201: Number(data[\"cpm2.5\"]),\n                 202: Number(data[\"cpm10\"]),\n                 203: Number(data[\"apm1.0\"]),\n                 204: Number(data[\"apm2.5\"]),\n                 205: Number(data[\"apm10\"]),\n                 206: Number(data[\"aqi\"]),\n                 207: data[\"t\"],\n                 208: Number(data[\"r\"])\n              }\n              break;\n        case \"6\":\n              answ.data.dps[data.res] = \"done \";\n              break;\n        case \"10\":       // start historical data\n              flow.set(\"skipdata\", true);\n              return;\n        case \"80\":\n              answ.data.dps = {\n                 1 : Number(data[\"SendInteralTime\"]),\n                 2 : Number(data[\"StoreInteralTime\"]),\n                 80: Number(data[\"WritePoint\"]),\n                 81: Number(data[\"ReadPoint\"]),\n                 82: Number(data[\"SendInteralFlag\"])\n              }\n              break;\n\n   }\nmsg.payload = answ;    \nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1070,"y":420,"wires":[["c72cf7d0.7ff3c8"]]},{"id":"bf4e83f7.f5f21","type":"link in","z":"ee008879.f5adc8","name":"from core.fake_cmds","links":["18817677.061b9a","96b99b98.5c7788"],"x":35,"y":320,"wires":[["fa5e5a63.b11488"]]},{"id":"c72cf7d0.7ff3c8","type":"link out","z":"ee008879.f5adc8","name":"to core.logging","links":["9fe80f7e.f3f7e"],"x":1275,"y":420,"wires":[]},{"id":"7a6b37c.31c36c8","type":"timerswitch","z":"ee008879.f5adc8","name":"every hour","ontopic":"","offtopic":"","onpayload":"{\"fun\":\"05\",\"flag\":\"0\"}","offpayload":"null","disabled":false,"schedules":[{"on_h":"00","on_m":"58","on_s":"00","off_h":"00","off_m":"59","off_s":"00","valid":true},{"on_h":"01","on_m":"58","on_s":"00","off_h":"01","off_m":"59","off_s":"00","valid":true},{"on_h":"02","on_m":"58","on_s":"00","off_h":"02","off_m":"59","off_s":"00","valid":true},{"on_h":"03","on_m":"58","on_s":"00","off_h":"03","off_m":"59","off_s":"00","valid":true},{"on_h":"04","on_m":"58","on_s":"00","off_h":"04","off_m":"59","off_s":"00","valid":true},{"on_h":"05","on_m":"58","on_s":"00","off_h":"05","off_m":"59","off_s":"00","valid":true},{"on_h":"06","on_m":"58","on_s":"00","off_h":"06","off_m":"59","off_s":"00","valid":true},{"on_h":"07","on_m":"58","on_s":"00","off_h":"07","off_m":"59","off_s":"00","valid":true},{"on_h":"08","on_m":"58","on_s":"00","off_h":"08","off_m":"59","off_s":"00","valid":true},{"on_h":"09","on_m":"58","on_s":"00","off_h":"09","off_m":"59","off_s":"00","valid":true},{"on_h":"10","on_m":"58","on_s":"00","off_h":"10","off_m":"59","off_s":"00","valid":true},{"on_h":"11","on_m":"58","on_s":"00","off_h":"11","off_m":"59","off_s":"00","valid":true},{"on_h":"12","on_m":"58","on_s":"00","off_h":"12","off_m":"59","off_s":"00","valid":true},{"on_h":"13","on_m":"58","on_s":"00","off_h":"13","off_m":"59","off_s":"00","valid":true},{"on_h":"14","on_m":"58","on_s":"00","off_h":"14","off_m":"59","off_s":"00","valid":true},{"on_h":"15","on_m":"58","on_s":"00","off_h":"15","off_m":"59","off_s":"00","valid":true},{"on_h":"16","on_m":"58","on_s":"00","off_h":"16","off_m":"59","off_s":"00","valid":true},{"on_h":"17","on_m":"58","on_s":"00","off_h":"17","off_m":"59","off_s":"00","valid":true},{"on_h":"18","on_m":"58","on_s":"00","off_h":"18","off_m":"59","off_s":"00","valid":true},{"on_h":"19","on_m":"58","on_s":"00","off_h":"19","off_m":"59","off_s":"00","valid":true},{"on_h":"20","on_m":"58","on_s":"00","off_h":"20","off_m":"59","off_s":"00","valid":true},{"on_h":"21","on_m":"58","on_s":"00","off_h":"21","off_m":"59","off_s":"00","valid":true},{"on_h":"22","on_m":"58","on_s":"00","off_h":"22","off_m":"59","off_s":"00","valid":true},{"on_h":"23","on_m":"58","on_s":"00","off_h":"23","off_m":"59","off_s":"00","valid":true}],"x":415,"y":280,"wires":[["bb7ed790.b50db8","8ce5e629.3c5ef8"]],"l":false},{"id":"bb7ed790.b50db8","type":"delay","z":"ee008879.f5adc8","name":"","pauseType":"delay","timeout":"200","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":610,"y":240,"wires":[["5a5d640b.67b15c"]]},{"id":"5a5d640b.67b15c","type":"function","z":"ee008879.f5adc8","name":"start dump","func":"var sum = {\n    \"cpm1.0\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"cpm2.5\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"cpm10\" :[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"apm1.0\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"apm2.5\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"apm10\" :[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"aqi\"   :[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"t\"     :[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n    \"r\"     :[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n}\nvar count =  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];\n\nflow.set(\"sum\",sum);\nflow.set(\"count\",count);\nmsg.payload ={\"fun\":\"04\"};\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":830,"y":240,"wires":[["8ce5e629.3c5ef8"]]},{"id":"3787ab01.6ee494","type":"function","z":"ee008879.f5adc8","name":"Historical data processing","func":"\nif(flow.get(\"skipdata\") === false) return;\n\n// out: cmd/histo\nvar data = JSON.parse(msg.payload);\n\nvar answ = {\n    deviceId: \"_PM1_USB\",\n    data: {\n        t: Math.floor(Date.now() / 1000),\n        dps: {}\n    }\n}\n\nvar cmd = {\n    payload: {\n        \"fun\":\"05\", \n        \"flag\":\"1\"\n    }\n}\n\nswitch (data.res) {\n\ncase \"0\":   // end historical data\n    sum = flow.get(\"sum\");\n    count = flow.get(\"count\");\n    for (i = 0; i < 24; i++) {\n        if (count[i] > 0) {\n            sum[\"cpm1.0\"][i] = Math.round(sum[\"cpm1.0\"][i] / count[i]);\n            sum[\"cpm2.5\"][i] = Math.round(sum[\"cpm2.5\"][i] / count[i]);\n            sum[\"cpm10\"][i]  = Math.round(sum[\"cpm10\"][i] / count[i]);\n            sum[\"apm1.0\"][i] = Math.round(sum[\"apm1.0\"][i] / count[i]);\n            sum[\"apm2.5\"][i] = Math.round(sum[\"apm2.5\"][i] / count[i]);\n            sum[\"apm10\"][i]  = Math.round(sum[\"apm10\"][i] / count[i]);\n            sum[\"aqi\"][i]    = Math.round(sum[\"aqi\"][i] / count[i]);\n            sum[\"t\"][i]      = (sum[\"t\"][i] / count[i]).toFixed(1);\n            sum[\"r\"][i]      = (sum[\"r\"][i] / count[i]).toFixed(1);\n        }\n    }\n    answ.data.dps[300] = \"[\"+sum[\"cpm1.0\"].toString() + \"]\";\n    answ.data.dps[301] = \"[\"+sum[\"cpm2.5\"].toString() + \"]\";\n    answ.data.dps[302] = \"[\"+sum[\"cpm10\"].toString() + \"]\";\n    answ.data.dps[303] = \"[\"+sum[\"apm1.0\"].toString() + \"]\";\n    answ.data.dps[304] = \"[\"+sum[\"apm2.5\"].toString() + \"]\";\n    answ.data.dps[305] = \"[\"+sum[\"apm10\"].toString() + \"]\";\n    answ.data.dps[306] = \"[\"+sum[\"aqi\"].toString() + \"]\";\n    answ.data.dps[307] = \"[\"+sum[\"t\"].toString() + \"]\";\n    answ.data.dps[308] = \"[\"+sum[\"r\"].toString() + \"]\";\n    msg.payload = answ;\n    flow.set(\"skipdata\", false);\n    return [cmd, msg];\n\ncase \"4\":\n    sum = flow.get(\"sum\");\n    count = flow.get(\"count\");\n    count[Number(data[\"h\"])]++;\n    sum[\"cpm1.0\"][Number(data[\"h\"])] += Number(data[\"cpm1.0\"]);\n    sum[\"cpm2.5\"][Number(data[\"h\"])] += Number(data[\"cpm2.5\"]);\n    sum[\"cpm10\"][Number(data[\"h\"])]  += Number(data[\"cpm10\"]);\n    sum[\"apm1.0\"][Number(data[\"h\"])] += Number(data[\"apm1.0\"]);\n    sum[\"apm2.5\"][Number(data[\"h\"])] += Number(data[\"apm2.5\"]);\n    sum[\"apm10\"][Number(data[\"h\"])]  += Number(data[\"apm10\"]);\n    sum[\"aqi\"][Number(data[\"h\"])]    += Number(data[\"aqi\"]);\n    sum[\"t\"][Number(data[\"h\"])]      += Number(data[\"t\"]);\n    sum[\"r\"][Number(data[\"h\"])]      += Number(data[\"r\"]);\n\n    flow.set(\"sum\", sum);\n    flow.set(\"count\", count);\n    return;\n\ncase \"10\":\n    return;\n}\nreturn;\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":1090,"y":380,"wires":[["8ce5e629.3c5ef8"],["c72cf7d0.7ff3c8"]]},{"id":"260f2555.1d5ffa","type":"status","z":"ee008879.f5adc8","name":"spy status","scope":["e37f5692.37de78","28f4d04b.ed1b7"],"x":820,"y":480,"wires":[["bcf0461e.c12788"]]},{"id":"bcf0461e.c12788","type":"subflow:ee953c15.ada29","z":"ee008879.f5adc8","name":"","env":[{"name":"deviceId","value":"_PM1_USB","type":"str"},{"name":"deviceID","value":"device20-id****","type":"str"}],"x":1050,"y":480,"wires":[["4fb32195.592ef"]]},{"id":"4fb32195.592ef","type":"link out","z":"ee008879.f5adc8","name":"to core.more_devices_in","links":["c90b7eb2.07595"],"x":1275,"y":480,"wires":[]},{"id":"21a669e.61ac096","type":"inject","z":"ee008879.f5adc8","name":"CLEAR MEMORY","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"01 00 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"{\"fun\":\"06\"}","payloadType":"json","x":415,"y":380,"wires":[["8ce5e629.3c5ef8"]],"icon":"node-red-contrib-eztimer/datetime.png","l":false},{"id":"ce13d88d.13bf18","type":"inject","z":"ee008879.f5adc8","name":"TEST Historical","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"fun\":\"05\",\"flag\":\"0\"}","payloadType":"json","x":180,"y":280,"wires":[["bb7ed790.b50db8","8ce5e629.3c5ef8"]]},{"id":"608b2442.af601c","type":"comment","z":"ee008879.f5adc8","name":"custom 'PM detector '","info":"Flow to control the 'PM detector' via USB-serial\n\nnote: You can fire some tests, used for debug and as use example. You can also delete it.\n\nsee: https://github.com/msillano/tuyaDAEMON/wiki/custom-device-'PM-detector':-case-study","x":160,"y":60,"wires":[]},{"id":"7454f585.417bcc","type":"link in","z":"ee008879.f5adc8","name":"from system.start_DAEMON","links":["37306de6.0a7f42"],"x":355,"y":100,"wires":[["d53018ba.5b1448"]]},{"id":"1282a87d.494a98","type":"change","z":"ee008879.f5adc8","name":"init sendtime","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"device\":\"PM detector\",\"property\":\"sendtime\",\"value\":\"120\"}","tot":"json"},{"t":"set","p":"to","pt":"msg","to":"_PM1_USB","tot":"str"},{"t":"set","p":"infodp","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":40,"wires":[["96b99b98.5c7788","271a5b46.b66224"]]},{"id":"271a5b46.b66224","type":"change","z":"ee008879.f5adc8","name":"Init storetime","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"device\":\"PM detector\",\"property\":\"storetime\",\"value\":\"700\"}","tot":"json"},{"t":"set","p":"to","pt":"msg","to":"_PM1_USB","tot":"str"},{"t":"set","p":"infodp","pt":"msg","to":"2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":80,"wires":[["96b99b98.5c7788","b9e8d491.2f8e58"]]},{"id":"136ad89e.c0d117","type":"change","z":"ee008879.f5adc8","name":"Init RT","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"device\":\"PM detector\",\"property\":\"startRT\",\"value\":\"1\"}","tot":"json"},{"t":"set","p":"to","pt":"msg","to":"_PM1_USB","tot":"str"},{"t":"set","p":"infodp","pt":"msg","to":"5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":690,"y":160,"wires":[["96b99b98.5c7788"]]},{"id":"2ebe64d5.26754c","type":"link out","z":"ee008879.f5adc8","name":"to core.std_cmd","links":["8a1da02d.424ae"],"x":355,"y":520,"wires":[]},{"id":"eefb4a7d.922138","type":"inject","z":"ee008879.f5adc8","name":"STOP RT","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"_PM1_USB\",\"property\":\"5\",\"value\":\"OFF\"}","payloadType":"json","x":160,"y":460,"wires":[["2ebe64d5.26754c"]]},{"id":"db6536d1.c90638","type":"inject","z":"ee008879.f5adc8","name":"START RT","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"_PM1_USB\",\"property\":\"5\",\"value\":\"ON\"}","payloadType":"json","x":160,"y":500,"wires":[["2ebe64d5.26754c"]]},{"id":"b9e8d491.2f8e58","type":"change","z":"ee008879.f5adc8","name":"Init clock","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"device\":\"PM detector\",\"property\":\"set clock\",\"value\":\"\"}","tot":"json"},{"t":"set","p":"to","pt":"msg","to":"_PM1_USB","tot":"str"},{"t":"set","p":"infodp","pt":"msg","to":"3","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":120,"wires":[["96b99b98.5c7788","136ad89e.c0d117"]]},{"id":"96b99b98.5c7788","type":"link out","z":"ee008879.f5adc8","name":"to in cmd","links":["bf4e83f7.f5f21"],"x":915,"y":140,"wires":[],"icon":"font-awesome/fa-expand"},{"id":"a4c72b18.762b68","type":"inject","z":"ee008879.f5adc8","name":"FAST RT","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"_PM1_USB\",\"property\":\"1\",\"value\":20}","payloadType":"json","x":160,"y":540,"wires":[["2ebe64d5.26754c"]]},{"id":"43e311a3.08147","type":"inject","z":"ee008879.f5adc8","name":"SLOW RT","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"_PM1_USB\",\"property\":\"1\",\"value\":120}","payloadType":"json","x":160,"y":580,"wires":[["2ebe64d5.26754c"]]},{"id":"e914f703.e257b8","type":"link out","z":"ee008879.f5adc8","name":"to core.logging","links":["9fe80f7e.f3f7e"],"x":355,"y":340,"wires":[]},{"id":"b163f8df.fd9278","type":"inject","z":"ee008879.f5adc8","name":"PM detector SET REFRESH","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"PM detector\",\"property\":\"_refresh\",\"value\":\"now\"}","payloadType":"json","x":220,"y":720,"wires":[["78b729bf.554958"]]},{"id":"78b729bf.554958","type":"link out","z":"ee008879.f5adc8","name":"to core.std_cmd","links":["8a1da02d.424ae"],"x":435,"y":720,"wires":[]},{"id":"87fd0e9c.cb79","type":"inject","z":"ee008879.f5adc8","name":"PM detector GET REFRESH","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"PM detector\",\"property\":\"_refresh\"}","payloadType":"json","x":220,"y":760,"wires":[["78b729bf.554958"]]},{"id":"8bf3f614.b791d8","type":"inject","z":"ee008879.f5adc8","name":"PM detector SET _refreshCycle 10s","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"PM detector\",\"property\":\"_refreshCycle\",\"value\":10}","payloadType":"json","x":680,"y":760,"wires":[["270607f1.95ddd8"]]},{"id":"270607f1.95ddd8","type":"link out","z":"ee008879.f5adc8","name":"to core.std_cmd","links":["8a1da02d.424ae"],"x":935,"y":820,"wires":[]},{"id":"68d77188.dcd4c","type":"inject","z":"ee008879.f5adc8","name":"PM detector SET _refreshCycle 120s","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"PM detector\",\"property\":\"_refreshCycle\",\"value\":120}","payloadType":"json","x":690,"y":720,"wires":[["270607f1.95ddd8"]]},{"id":"1107bea7.312231","type":"inject","z":"ee008879.f5adc8","name":"PM detector GET _refreshCycle","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"PM detector\",\"property\":\"_refreshCycle\"}","payloadType":"json","x":670,"y":860,"wires":[["270607f1.95ddd8"]]},{"id":"ab2b3522.0fae28","type":"inject","z":"ee008879.f5adc8","name":"PM detector SET _refreshCycle STOP","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"PM detector\",\"property\":\"_refreshCycle\",\"value\":\"off\"}","payloadType":"json","x":690,"y":820,"wires":[["270607f1.95ddd8"]]},{"id":"8ce5e629.3c5ef8","type":"delay","z":"ee008879.f5adc8","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"20","nbRateUnits":"10","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":600,"y":320,"wires":[["28f4d04b.ed1b7"]]},{"id":"42829377.f058dc","type":"inject","z":"ee008879.f5adc8","name":"PM detector GET RUN","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"device\":\"PM detector\",\"property\":\"startRT\"}","payloadType":"json","x":240,"y":820,"wires":[["78b729bf.554958"]]},{"id":"d53018ba.5b1448","type":"delay","z":"ee008879.f5adc8","name":"","pauseType":"delay","timeout":"750","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":480,"y":100,"wires":[["1282a87d.494a98"]]},{"id":"45a4b402.6f7e4c","type":"serial-port","serialport":"COM19","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"{","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"}","bin":"false","out":"char","addchar":"\\n","responsetimeout":"10000"}]

Flow Info

Created 3 years, 2 months ago
Rating: 5 2

Owner

Actions

Rate:

Node Types

Core
  • change (x4)
  • comment (x1)
  • delay (x3)
  • function (x5)
  • inject (x13)
  • link in (x2)
  • link out (x7)
  • status (x1)
Other
  • serial in (x1)
  • serial out (x1)
  • serial-port (x1)
  • subflow (x1)
  • subflow:ee953c15.ada29 (x1)
  • tab (x1)
  • timerswitch (x1)

Tags

  • IOT
  • smart-device
  • MQTT
  • custom
  • framework
  • home-automation
  • Tuya
  • trigger
  • device
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option