NotEnoughTech
Published © CC BY-NC-SA

Smart Door

A door, just smarter. Retrofit "smarts" into the traditional entrance and never leave your door unlocked again!

IntermediateFull instructions provided2 hours997
Smart Door

Things used in this project

Hardware components

Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
Tuya Door Sensor
×2
cc2531 Zigbee USB
×1

Software apps and online services

Node-RED
Node-RED

Hand tools and fabrication machines

Drill / Driver, Cordless
Drill / Driver, Cordless

Story

Read more

Code

NodeRED Flow

JavaScript
[{"id":"e58c953a.902438","type":"subflow","name":"Smart Door Config","info":"","category":"","in":[{"x":220,"y":80,"wires":[{"id":"3699a88b.66c178"}]}],"out":[{"x":480,"y":80,"wires":[{"id":"3699a88b.66c178","port":0}]}],"env":[{"name":"Door_timeout","type":"num","value":""},{"name":"Lock_timeout","type":"num","value":""}],"color":"#DDAA99"},{"id":"3699a88b.66c178","type":"function","z":"e58c953a.902438","name":"SetTimout","func":"var d_timout = env.get(\"Door_timeout\");\nvar l_timeout = env.get(\"Lock_timeout\");\n\nfunction ttms(minutes){\n    this.timems = minutes * 60000\n    return timems;\n}\n\nflow.set(\"$parent.DoorTimeout\", ttms(d_timout));\nflow.set(\"$parent.LockTimeout\", ttms(l_timeout));\n\nmsg.payload = \"Timeouts has been set\";\n\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":80,"wires":[[]]},{"id":"4b1cc6cf.4eeef8","type":"tab","label":"Smart Door","disabled":false,"info":"\r\n![enter image description here](https://notenoughtech.com/wp-content/uploads/2020/03/maxresdefault-9.jpg)\r\nWould it be nice if the door let you know if you forgot to lock it? Thanks to this project, you can remotely check if the door is locked and receive notifications when you forget to lock the door behind you! \r\n\r\n - [Complete instructions](https://notenoughtech.com/home-automation/retrofit-smart-door-on-a-budget/)\r\n\r\n**Features**:\r\n-   **notifies when left unlocked (Android, Alexa, Google Home)**\r\n-   **notifies when left open for a prolonged time (Android, Alexa, Google Home)**\r\n-   **Alexa & Google Assistant integration (query door state)**\r\n-   **bedtime reminders if the door is unlocked**\r\n-   **Geofence based notification (Android + Tasker)**\r\n\r\n## Requirements\r\n\r\n-   `node-red-contrib-nora (_Google Assistant query_)`\r\n-   `node-red-contrib-alexa-home-skill (_Alexa query_)`\r\n-   `node-red-contrib-alexa-remote2 (_Alexa notification_)`\r\n-   `node-red-contrib-castv2 (_Google Assistant notification_)`\r\n-   `node-red-contrib-join-joaoapps (_Android notification_)`\r\n\r\nContact Sensors:\r\n\r\n- [Used by me: Benexmart Zigbee Contact sensor](https://s.click.aliexpress.com/e/_d9egHI9)\r\n-   **other door sensors x 2**  ([AliExpress](https://s.click.aliexpress.com/e/_dWJDnw5),  [Banggood](https://www.banggood.com/custlink/GKmvM5vUNB),  [Gearbest](https://www.gearbest.com/sale/zigbee-door-sensor/?lkid=78716289),  [AmazonUK](https://amzn.to/2w0MxC0),  [AmazonUS](https://amzn.to/3aVcvWj))\r\n-   **Neodymium magnet x 1**  ([AliExpress](https://s.click.aliexpress.com/e/_dUM9gxT))\r\n \r\n\r\n\r\n# Settings\r\n\r\n - **timeout door** - in minutes, alarm for door being left open\r\n -  **timeout lock** - in minutes, alarm for lock left unlocked\r\n - Tasker profile available on [TaskerNet](https://taskernet.com/shares/?user=AS35m8mukRNi0KvTv9JF6LyKMewcG%2BW7EzpF3a%2FGvwJJTFwWQ1%2BU3QCMEmxTS%2F07urNK%2FTAL&id=Project%3ASmart+Door).\r\n \r\n ## More about me:\r\n\r\nIf you want to get the latest updates to this project you can follow me via your preferred social media:\r\n\r\n-   [Facebook](https://www.facebook.com/NotEnoughTECH/)\r\n-   [Twitter](https://twitter.com/NotEnoughTECH)\r\n-   [Instagram](https://www.instagram.com/notenoughtech/)\r\n-   [YouTube](https://www.youtube.com/user/Polepositionpage)\r\n\r\nAnd if you feeling like buying me a coffee or supporting me in a more continuous way:\r\n\r\n-   [Paypal](https://www.paypal.me/notenoughtech)\r\n-   [Patreon](https://www.patreon.com/NotEnoughTECH)\r\n\r\nI hope you have enjoyed the project!\r\n\r\n "},{"id":"f3d3abcf.9447f8","type":"function","z":"4b1cc6cf.4eeef8","name":"Save status","func":"var timestamp_o, timestamp_c, door, doorData;\n\ndoorData = flow.get(\"f_door_status\");\n\n//first time use\nif(typeof doorData === 'undefined') {\n    door = {\n    \"contact\" : null,\n    \"battery_low\": 0,\n    \"battery\" : 0,\n    \"time_o\": 0,\n    \"time_c\": 0\n    };\n    flow.set(\"f_door_status\", door);\n}\n\n//on close\nif(msg.payload.contact === true){\n    timestamp_c = Date.now();\n    timestamp_o = doorData.time_o;\n}\n//on open\nif(msg.payload.contact === false){\n    timestamp_o = Date.now();\n    timestamp_c = doorData.time_c;\n}\n\ndoor = {\n    \"contact\" : msg.payload.contact,\n    \"battery_low\": msg.payload.battery_low,\n    \"battery\" : msg.payload.battery,\n    \"time_o\": timestamp_o,\n    \"time_c\": timestamp_c\n}\n\nflow.set(\"f_door_status\", door);\n","outputs":1,"noerr":0,"x":490,"y":300,"wires":[[]]},{"id":"e29626e5.a4e2b8","type":"function","z":"4b1cc6cf.4eeef8","name":"Save status","func":"var timestamp_o, timestamp_c, lock, lockData;\n\n//get previous values\nlockData = flow.get(\"f_lock_status\");\n\n//first time use\nif(typeof lockData === 'undefined') {\n    lock = {\n    \"contact\" : null,\n    \"battery_low\": 0,\n    \"battery\" : 0,\n    \"time_o\": 0,\n    \"time_c\": 0\n    };\n    flow.set(\"f_lock_status\", lock);\n}\n//on lock\nif(msg.payload.contact === true){\n    timestamp_c = Date.now();\n    timestamp_o = lockData.time_o;\n}\n//on unlock\nif(msg.payload.contact === false){\n    timestamp_o = Date.now();\n    timestamp_c = lockData.time_c;\n}\n\nlock = {\n    \"contact\" : msg.payload.contact,\n    \"battery_low\": msg.payload.battery_low,\n    \"battery\" : msg.payload.battery,\n    \"time_o\": timestamp_o,\n    \"time_c\": timestamp_c\n}\n\nflow.set(\"f_lock_status\", lock);\n\n\n","outputs":1,"noerr":0,"x":490,"y":420,"wires":[[]]},{"id":"4baa7a54.e81664","type":"rbe","z":"4b1cc6cf.4eeef8","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload.contact","x":290,"y":320,"wires":[["f3d3abcf.9447f8","a72f2813.2aa038","fde5f94b.3017d8"]]},{"id":"ea4c0dfc.5873d","type":"rbe","z":"4b1cc6cf.4eeef8","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload.contact","x":290,"y":380,"wires":[["e29626e5.a4e2b8","9ee26f07.81445","fde5f94b.3017d8"]]},{"id":"21c17f50.bc923","type":"mqtt in","z":"4b1cc6cf.4eeef8","name":"Door","topic":"zigbee2mqtt/Door_front","qos":"2","datatype":"json","broker":"6e117ba5.8026e4","x":150,"y":320,"wires":[["4baa7a54.e81664"]]},{"id":"f46f81e8.38a15","type":"mqtt in","z":"4b1cc6cf.4eeef8","name":"Lock","topic":"zigbee2mqtt/Door_front_lock","qos":"2","datatype":"json","broker":"6e117ba5.8026e4","x":150,"y":380,"wires":[["ea4c0dfc.5873d"]]},{"id":"1cffd96f.9c8077","type":"subflow:e58c953a.902438","z":"4b1cc6cf.4eeef8","name":"Config","env":[{"name":"Door_timeout","value":"5","type":"num"},{"name":"Lock_timeout","value":"2","type":"num"}],"x":330,"y":100,"wires":[["80ec542b.92ab28"]]},{"id":"509fc92.1067d38","type":"inject","z":"4b1cc6cf.4eeef8","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":100,"wires":[["1cffd96f.9c8077"]]},{"id":"80ec542b.92ab28","type":"debug","z":"4b1cc6cf.4eeef8","name":"Default Timeouts","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":510,"y":100,"wires":[]},{"id":"3bd143f6.81af2c","type":"function","z":"4b1cc6cf.4eeef8","name":"Door Timeout","func":"var alarm, timeout, timer;\n\n//set alarm\nalarm = Date.now();\ntimeout = flow.get(\"DoorTimeout\");\ntimer = timeout + alarm;\n//save alarm\nflow.set(\"DoorAlarm_state\", true);\nflow.set(\"DoorAlarm\", timer);\n\n//disable lock alarm and reset\nflow.set(\"LockAlarm_state\", false);\nflow.set(\"LockAlarm\", 0);\n\n","outputs":1,"noerr":0,"x":680,"y":220,"wires":[[]]},{"id":"a0e5a5b4.480e38","type":"function","z":"4b1cc6cf.4eeef8","name":"Lock Timeout","func":"var alarm, timer, timout;\n\n//set alarm\nalarm = Date.now();\ntimeout = flow.get(\"LockTimeout\");\ntimer = timeout + alarm;\n\n//save alarm\nflow.set(\"LockAlarm_state\", true);\nflow.set(\"LockAlarm\", timer);\n\n","outputs":1,"noerr":0,"x":670,"y":460,"wires":[[]]},{"id":"9ee26f07.81445","type":"switch","z":"4b1cc6cf.4eeef8","name":"","property":"payload.contact","propertyType":"msg","rules":[{"t":"false"},{"t":"true"}],"checkall":"true","repair":false,"outputs":2,"x":510,"y":480,"wires":[["a0e5a5b4.480e38"],["589fd0f4.0f04"]]},{"id":"a72f2813.2aa038","type":"switch","z":"4b1cc6cf.4eeef8","name":"","property":"payload.contact","propertyType":"msg","rules":[{"t":"false"},{"t":"true"}],"checkall":"true","repair":false,"outputs":2,"x":510,"y":240,"wires":[["3bd143f6.81af2c"],["50e99e5b.1f326"]]},{"id":"50e99e5b.1f326","type":"function","z":"4b1cc6cf.4eeef8","name":"Door Closed","func":"var timeout, alarm, timer;\n\n//reset door alarm\nflow.set(\"DoorAlarm_state\", false);\nflow.set(\"DoorAlarm\", 0);\n\n\n\n//arm lock\nalarm = Date.now();\ntimeout = flow.get(\"LockTimeout\");\ntimer = timeout + alarm;\n\n//update status\nflow.set(\"LockAlarm_state\", true);\nflow.set(\"LockAlarm\", timer);\n\n//reset warning status\nflow.set(\"warning\", false);\n\n","outputs":1,"noerr":0,"x":670,"y":260,"wires":[[]]},{"id":"589fd0f4.0f04","type":"function","z":"4b1cc6cf.4eeef8","name":"Lock on","func":"var warning, id1;\n\n// reset lock alarm\nflow.set(\"LockAlarm_state\", false);\nflow.set(\"LockAlarm\", 0);\n\n\nwarning = flow.get(\"warning\");\nid1  = flow.get(\"JOIN_mi9\");  //add your join devices via global ID\n\n//if warning was sent before - send a confirmation\nif(warning === true){\n    msg.push = sendPush(id1);\n    flow.set(\"warning\", false);\n    return msg;\n    \n}\n\n\nfunction sendPush(id){\n    let push = {\n        \"deviceIds\": id1,\n        \"title\":\"Smart Door Alert\",\n        \"text\":\"Your door now lock and loaded - carry on with your day\",\n        \"icon\":\"https://img.icons8.com/dusk/64/000000/door-closed.png\"   //https://drive.google.com/uc?export=view&id=  [image id in incognito to generate long URL]\n    };\nreturn push;\n}\n","outputs":1,"noerr":0,"x":660,"y":500,"wires":[["8a429576.d0c828"]]},{"id":"e5df6f20.b7ef6","type":"inject","z":"4b1cc6cf.4eeef8","name":"","topic":"","payload":"","payloadType":"date","repeat":"10","crontab":"","once":true,"onceDelay":0.1,"x":170,"y":640,"wires":[["5f1c22fa.04791c"]]},{"id":"5f1c22fa.04791c","type":"function","z":"4b1cc6cf.4eeef8","name":"Timed Notifications","func":"var warning, timenow, id1;\nvar doorAlarm, door, doorAlarm_state;\nvar lockAlarm, lock, lockAlarm_state;\n\n\ntimenow = msg.payload;\nwarning = flow.get(\"warning\");\nid1 = global.get(\"JOIN_mi9\"); //add your join devices via global ID\n\n// get door info\ndoorAlarm = flow.get(\"DoorAlarm\");\ndoor = flow.get(\"f_door_status\");\ndoorAlarm_state = flow.get(\"DoorAlarm_state\");\n\n\n// get lock info\nlockAlarm = flow.get(\"LockAlarm\");\nlock = flow.get(\"f_lock_status\");\nlockAlarm_state = flow.get(\"LockAlarm_state\");\n\n\n\n//if door is left open - disregards lock alarm\nif(warning === false){\n    if(doorAlarm_state === true && timenow > doorAlarm){\n        msg.push = sendPush(id1, door.contact, lock.contact);\n        flow.set(\"warning\", true);\n        return msg;\n    }\n    \n    //if door is left unlocked but closed\n    if(lockAlarm_state === true && timenow > lockAlarm){\n        msg.push = sendPush(id1, door.contact, lock.contact);\n        flow.set(\"warning\", true);\n        return msg;\n    }\n}\n\n\nfunction sendPush(id,door,lock){\n    let message;\n    if(door === false && lock === false){\n        //the door is open and unlocked\n        message = \"The front door is open\";\n    }\n    if(door === true && lock === false){\n        //the door is closed but unlocked\n        message = \"The front door isn't locked, you silly sausage!\";\n    }\n    if(door === true && lock === true){\n        //the door is closed and locked\n        message = \"You are locked and loaded\";\n    }\n    \n    let push = {\n        \"deviceIds\": id1,\n        \"title\":\"Front Door Alert\",\n        \"text\": message,\n        \"icon\":\"https://img.icons8.com/dusk/64/000000/door-closed.png\"   \n    };\nreturn push;\n}","outputs":1,"noerr":0,"x":690,"y":640,"wires":[["8a429576.d0c828"]]},{"id":"6958caef.b2e734","type":"comment","z":"4b1cc6cf.4eeef8","name":"Door Notification","info":"","x":140,"y":600,"wires":[]},{"id":"83d8284.bcc57d8","type":"comment","z":"4b1cc6cf.4eeef8","name":"Smart Speaker query","info":"","x":140,"y":920,"wires":[]},{"id":"56b5b38e.afa06c","type":"alexa-home","z":"4b1cc6cf.4eeef8","conf":"5c48c22a.3a371c","device":"95787","acknoledge":false,"name":"","topic":"","x":130,"y":1020,"wires":[["1c87b12c.e373ef"]]},{"id":"94db1ba4.105928","type":"alexa-home-resp","z":"4b1cc6cf.4eeef8","x":560,"y":1020,"wires":[]},{"id":"1c87b12c.e373ef","type":"function","z":"4b1cc6cf.4eeef8","name":"Response","func":"var lock, door;\n\nlock = flow.get(\"f_lock_status\");\ndoor = flow.get(\"f_door_status\");\n\n\n//lock and loaded\nif(lock.contact === true && door.contact === true){\n    msg.extra = {lockState: \"LOCKED\"};\n    msg.payload = true;\nreturn msg;\n}\n\n//unlocked\nif(lock.contact === false || door.contact === false){\n    msg.extra = {lockState: \"UNLOCKED\"};\n    msg.payload = true;\nreturn msg;\n}\n\n","outputs":1,"noerr":0,"x":320,"y":1020,"wires":[["94db1ba4.105928"]]},{"id":"fde5f94b.3017d8","type":"function","z":"4b1cc6cf.4eeef8","name":"Update Google","func":"var door, lock;\n\nlock = flow.get(\"f_lock_status\");\ndoor = flow.get(\"f_door_status\");\n\n\n//lock and loaded\nif(lock.contact === true && door.contact === true){\n    msg.payload = true;\nreturn msg;\n}\n\n//unlocked\nif(lock.contact === false || door.contact === false){\n    msg.payload = false;\nreturn msg;\n}\n\n","outputs":1,"noerr":0,"x":340,"y":960,"wires":[["fba2e307.d688d"]]},{"id":"2f26dd57.5cd972","type":"comment","z":"4b1cc6cf.4eeef8","name":"Timeout Settings","info":"","x":260,"y":60,"wires":[]},{"id":"8a429576.d0c828","type":"join-message","z":"4b1cc6cf.4eeef8","name":"","text":"","title":"","url":"","notificationicon":"","joinConfig":"8ac6a18d.55759","x":980,"y":640,"wires":[[]]},{"id":"bcf3a62.9081158","type":"inject","z":"4b1cc6cf.4eeef8","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"00 22 * * *","once":false,"onceDelay":0.1,"x":170,"y":780,"wires":[["91fbb2e5.f020c"]]},{"id":"91fbb2e5.f020c","type":"function","z":"4b1cc6cf.4eeef8","name":"Bedtime alarm","func":"var door, lock;\n\nlock = flow.get(\"f_lock_status\");\ndoor = flow.get(\"f_door_status\");\n\n\n//unlocked\nif(lock.contact === false || door.contact === false){\n    msg.payload = {\n        type: \"TTS\",\n        text: \"Your door isn't secured\",\n        speed: 1, \n        language: \"en\"};\n        \n    return msg;\n}\n\n\n\n","outputs":1,"noerr":0,"x":420,"y":780,"wires":[["4d3e4434.7a649c","d5d2c24e.3c6bf"]]},{"id":"4d3e4434.7a649c","type":"castv2-sender","z":"4b1cc6cf.4eeef8","name":"Goolge Mini","host":"192.168.1.174","port":"","x":650,"y":760,"wires":[[]]},{"id":"d5d2c24e.3c6bf","type":"alexa-remote-routine","z":"4b1cc6cf.4eeef8","name":"Echo dot","account":"9d26c7ce.162eb8","routineNode":{"type":"speak","payload":{"type":"regular","text":{"type":"str","value":"Your door is unlocked"},"devices":["G090VC0783930SDH"]}},"x":640,"y":820,"wires":[[]]},{"id":"909ed63a.9d3428","type":"comment","z":"4b1cc6cf.4eeef8","name":"Bedtime alarms","info":"","x":120,"y":740,"wires":[]},{"id":"fba2e307.d688d","type":"nora-lock","z":"4b1cc6cf.4eeef8","devicename":"Door","roomhint":"","name":"","passthru":false,"nora":"","topic":"","lockValue":"true","lockValueType":"bool","unlockValue":"false","unlockValueType":"bool","jammedValue":"true","jammedValueType":"bool","unjammedValue":"false","unjammedValueType":"bool","x":510,"y":960,"wires":[[]]},{"id":"557b76cc.5e63b8","type":"http in","z":"4b1cc6cf.4eeef8","name":"","url":"/smartdoor","method":"get","upload":false,"swaggerDoc":"","x":120,"y":1180,"wires":[["1f1278ed.3f72c7","a393412c.c5e4f"]]},{"id":"a070a1ca.2379f","type":"http response","z":"4b1cc6cf.4eeef8","name":"","statusCode":"","headers":{},"x":630,"y":1180,"wires":[]},{"id":"1f1278ed.3f72c7","type":"function","z":"4b1cc6cf.4eeef8","name":"HTTP Response","func":"var door, lock;\n\nlock = flow.get(\"f_lock_status\");\ndoor = flow.get(\"f_door_status\");\n\nmsg.payload = {\n    \"door\": door.contact,\n    \"lock\": lock.contact\n};\n\n\n\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":1180,"wires":[["a070a1ca.2379f"]]},{"id":"b52737ef.f78d58","type":"comment","z":"4b1cc6cf.4eeef8","name":"Tasker query","info":"","x":90,"y":1120,"wires":[]},{"id":"a393412c.c5e4f","type":"debug","z":"4b1cc6cf.4eeef8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":280,"y":1300,"wires":[]},{"id":"6e117ba5.8026e4","type":"mqtt-broker","z":"","name":"MQTT","broker":"automation.local","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"5c48c22a.3a371c","type":"alexa-home-conf","z":"","username":"notenoughtech"},{"id":"8ac6a18d.55759","type":"join-config","z":"","name":"JOIN Mat","register":true},{"id":"9d26c7ce.162eb8","type":"alexa-remote-account","z":"","name":"","authMethod":"proxy","proxyOwnIp":"192.168.1.107","proxyPort":"3456","cookieFile":"","refreshInterval":"3","alexaServiceHost":"alexa.amazon.co.uk","amazonPage":"amazon.co.uk","acceptLanguage":"en-UK","userAgent":"","useWsMqtt":"on","autoInit":"off"}]

Credits

NotEnoughTech

NotEnoughTech

9 projects • 59 followers
I'm Mat I play with gadgets, make new stuff and automate pretty much everything that requires repeating more than once!

Comments