├── callbackScripts ├── callback.sh ├── callback.py ├── callback.js └── default │ ├── displayOff.sh │ └── displayOn.sh ├── mmm-pir-style.css ├── screenshot.png ├── translations ├── en.json ├── de.json └── sv.json ├── package.json ├── node_helper.js ├── README.md └── MMM-PIR.js /callbackScripts/callback.sh: -------------------------------------------------------------------------------- 1 | echo "sh callback" -------------------------------------------------------------------------------- /callbackScripts/callback.py: -------------------------------------------------------------------------------- 1 | print("Python callback"); -------------------------------------------------------------------------------- /callbackScripts/callback.js: -------------------------------------------------------------------------------- 1 | console.log("JS callback"); -------------------------------------------------------------------------------- /mmm-pir-style.css: -------------------------------------------------------------------------------- 1 | .last{ 2 | font-size: small; 3 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mboskamp/MMM-PIR/HEAD/screenshot.png -------------------------------------------------------------------------------- /callbackScripts/default/displayOff.sh: -------------------------------------------------------------------------------- 1 | #tvservice -o 2 | vcgencmd display_power 0 3 | echo "turn off display" 4 | -------------------------------------------------------------------------------- /callbackScripts/default/displayOn.sh: -------------------------------------------------------------------------------- 1 | #tvservice -p 2 | vcgencmd display_power 1 3 | echo "turn on display" 4 | -------------------------------------------------------------------------------- /translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "HEADER": "Time until standby:", 3 | "LAST_USER_PRESENCE": "Last activity:", 4 | "TELEGRAM_COMMAND_ERROR": "Error while executing command `{command}`." 5 | } -------------------------------------------------------------------------------- /translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "HEADER": "Zeit bis Ruhezustand:", 3 | "LAST_USER_PRESENCE": "Letzte Aktivität:", 4 | "INVALID_PARAM_ERROR": "Fehler beim Ausführen des Kommandos: `{command}`." 5 | } -------------------------------------------------------------------------------- /translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "HEADER": "Tid kvar till stand-by läge:", 3 | "LAST_USER_PRESENCE": "Senaste aktivitet:", 4 | "TELEGRAM_COMMAND_ERROR": "Ett fel uppstod när ett kommando skulle utföras `{command}`." 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmm-pir", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "MMM-PIR.js", 6 | "dependencies": { 7 | "child_process": "^1.0.2", 8 | "onoff": "^2.0.0" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/mboskamp/MMM-PIR.git" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/mboskamp/MMM-PIR/issues" 22 | }, 23 | "homepage": "https://github.com/mboskamp/MMM-PIR#readme" 24 | } 25 | -------------------------------------------------------------------------------- /node_helper.js: -------------------------------------------------------------------------------- 1 | var NodeHelper = require("node_helper"); 2 | const exec = require('child_process').exec; 3 | const fs = require('fs'); 4 | const Gpio = require('onoff').Gpio; 5 | 6 | var commandDict = { 7 | "js": "node", 8 | "py": "python", 9 | "sh": "sh" 10 | }; 11 | 12 | module.exports = NodeHelper.create({ 13 | 14 | running: false, 15 | 16 | socketNotificationReceived: function (notification, payload) { 17 | const self = this; 18 | if (notification === "CONFIG") { 19 | this.config = payload; 20 | this.pir = new Gpio(this.config.sensorPin, 'in', 'both'); 21 | 22 | this.pir.watch(function (err, value) { 23 | if (value == 1) { 24 | self.sendSocketNotification("USER_PRESENCE", true); 25 | if (!self.running) { 26 | self.running = true; 27 | if (self.config.turnOffDisplay) { 28 | execute(buildCommand("/default/displayOn.sh"), function (stdout) { 29 | console.log(stdout); 30 | }); 31 | } 32 | } 33 | } 34 | }); 35 | } else if (notification === "TIMER_EXPIRED") { 36 | self.running = false; 37 | for (var i = 0; i < this.config.callbackScripts.length; i++) { 38 | execute(buildCommand(this.config.callbackScripts[i]), function (stdout) { 39 | console.log(stdout); 40 | }); 41 | } 42 | if (self.config.turnOffDisplay) { 43 | execute(buildCommand("/default/displayOff.sh"), function (stdout) { 44 | console.log(stdout); 45 | }); 46 | } 47 | } 48 | }, 49 | 50 | 51 | }); 52 | 53 | function buildCommand(fileName) { 54 | var file = __dirname + "/callbackScripts/" + fileName; 55 | var fileExtension = file.split(".").slice(-1).pop(); 56 | return commandDict[fileExtension] + " " + file; 57 | } 58 | 59 | function execute(command, callback) { 60 | exec(command, function (error, stdout, stderr) { 61 | if (error) { 62 | console.log(stderr); 63 | } else { 64 | callback(stdout); 65 | } 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMM-PIR 2 | MMM-PIR is a module for the [MagicMirror](https://github.com/MichMich/MagicMirror) project by [Michael Teeuw](https://github.com/MichMich). 3 | 4 | It uses a PIR sensor attached to your raspberry pi's GPIO pins to check for users. After a configurated time without any user interaction the display will turn off. You can specify additional scripts (sh, python and js are supported) that will run after the timeout. 5 | 6 | ## Screenshot 7 | 8 | 9 | ## Installation 10 | Cline the module into your MagicMirror module folder and execute `npm install` in the module's directory-. 11 | ``` 12 | git clone https://github.com/mboskamp/MMM-PIR.git 13 | cd MMM-PIR 14 | npm install 15 | ``` 16 | 17 | ## Configuration 18 | To display the module insert it in the config.js file. Here is an example: 19 | ``` 20 | { 21 | module: 'MMM-PIR', 22 | position: 'bottom_center', 23 | config: { 24 | sensorPin: 4, 25 | delay: 10000, 26 | turnOffDisplay: true, 27 | showCountdown: true, 28 | callbackScripts: ["callback.py"] 29 | } 30 | } 31 | ``` 32 | 33 |
34 | 35 | | Option | Description | Type | Default | 36 | | ------- | --- | --- | --- | 37 | | sensorPin | BCM-number of the pin | Integer | 4 | 38 | | delay | time before the mirror turns off the display if no user activity is detected. (in ms) | Integer | 10000 (10 seconds) | 39 | | turnOffDisplay | Should the display turn off after timeout? | Boolean | true | 40 | | showCountdown | Should the MagicMirror display the countdown on screen? | Boolean | true | 41 | | callbackScripts | Scripts that execute after the timeout. Scripts must be placed inside the `callbackScripts` folder. Supported script types: sh, py, js| Array of strings | none | 42 | 43 | **Note:** The callback scripts to switch the screen [on](https://github.com/mboskamp/MMM-PIR/blob/master/callbackScripts/default/displayOn.sh) and [off](https://github.com/mboskamp/MMM-PIR/blob/master/callbackScripts/default/displayOff.sh) feature two ways of disabling the video output to the monitor. By default, this module uses `vcgencmd`. The second option (`tvservice`) can be used by removing the `vcgencmd` command and include the commented `tvservice`command in each file. 44 | 45 | ## Telegram commands 46 | You can control this module via Telegram with the following commands. Note: You need to install [MMM-TelegramBot](https://github.com/eouia/MMM-TelegramBot) 47 | 48 | | Command | Description | 49 | | --- | --- | 50 | | /resetPir | Resets the countdown to configured settings. | 51 | | /resetPirDefaults | Reset the countdown to default settings. | 52 | | /setCustomPirCountdown | Configure a custom countdown (in seconds). | 53 | -------------------------------------------------------------------------------- /MMM-PIR.js: -------------------------------------------------------------------------------- 1 | Module.register("MMM-PIR", { 2 | 3 | defaults: { 4 | sensorPin: 4, 5 | delay: 10000, 6 | turnOffDisplay: true, 7 | showCountdown: true, 8 | callbackScripts: [] 9 | }, 10 | 11 | start: function () { 12 | Log.log(this.name + ' is started!'); 13 | this.sendSocketNotification("CONFIG", this.config); 14 | 15 | moment.locale(config.language); 16 | }, 17 | 18 | getDom: function () { 19 | if (this.config.showCountdown) { 20 | var self = this; 21 | 22 | var html = document.createElement("div"); 23 | html.className = "wrapper"; 24 | 25 | if (typeof self.counter !== "undefined") { 26 | var headline = document.createElement("div"); 27 | headline.className = "head"; 28 | headline.innerText = self.translate("HEADER"); 29 | html.appendChild(headline); 30 | 31 | var time = document.createElement("div"); 32 | time.className = "time"; 33 | time.innerText = formatMillis(this.counter); 34 | html.appendChild(time); 35 | 36 | if (typeof self.presence === "object") { 37 | var last = document.createElement("div"); 38 | last.className = "last"; 39 | last.innerText = self.translate("LAST_USER_PRESENCE") + " " + self.presence.format("dddd, LL HH:mm:ss"); 40 | html.appendChild(last); 41 | } 42 | 43 | } 44 | 45 | return html; 46 | } 47 | }, 48 | 49 | getStyles: function () { 50 | return ["mmm-pir-style.css"]; 51 | }, 52 | 53 | getScripts: function () { 54 | return ["moment.js"]; 55 | }, 56 | 57 | getCommands: function (commander) { 58 | commander.add({ 59 | command: 'resetPir', 60 | callback: 'resetCountdown', 61 | description: 'Resets the countdown to configured settings.', 62 | }); 63 | commander.add({ 64 | command: 'resetPirDefaults', 65 | callback: 'resetDefaults', 66 | description: 'Reset the countdown to default settings.', 67 | }); 68 | commander.add({ 69 | command: 'setCustomPirCountdown', 70 | callback: 'setCustomCountdown', 71 | description: 'Configure a custom countdown. (in seconds)' 72 | }); 73 | }, 74 | 75 | getTranslations: function () { 76 | return { 77 | en: "translations/en.json", 78 | de: "translations/de.json" 79 | } 80 | }, 81 | 82 | socketNotificationReceived: function (notification, payload) { 83 | if (notification === "USER_PRESENCE") { 84 | this.presence_temp = moment(); 85 | this.resetCountdown(); 86 | } 87 | }, 88 | 89 | notificationReceived: function (notification, payload) { 90 | if (notification === 'DOM_OBJECTS_CREATED') { 91 | //DOM creation complete, let's start the module 92 | this.resetCountdown(); 93 | } 94 | }, 95 | 96 | resetDefaults: function () { 97 | this.counter = this.config.delay; 98 | }, 99 | 100 | resetCountdown: function () { 101 | var self = this; 102 | clearInterval(self.interval); 103 | if (self.customCounter != null) { 104 | self.counter = this.customCounter; 105 | } else { 106 | self.resetDefaults(); 107 | } 108 | self.updateDom(); 109 | self.interval = setInterval(function () { 110 | self.counter -= 1000; 111 | if (self.counter <= 0) { 112 | self.presence = self.presence_temp; 113 | self.sendSocketNotification('TIMER_EXPIRED'); 114 | clearInterval(self.interval); 115 | } 116 | self.updateDom(); 117 | }, 1000); 118 | }, 119 | 120 | setCustomCountdown: function (commander, handler) { 121 | var ccd = parseInt(handler.args); 122 | if (isNaN(ccd) || ccd < 1) { 123 | handler.reply("TEXT", this.translate("TELEGRAM_COMMAND_ERROR", {"command": "setCustomPirCountdown"})); 124 | } else { 125 | this.customCounter = ccd * 1000; 126 | this.resetCountdown(); 127 | } 128 | } 129 | }); 130 | 131 | function formatMillis(millis) { 132 | return new Date(millis).toUTCString().match(/\d{2}:\d{2}:\d{2}/)[0]; 133 | } --------------------------------------------------------------------------------