├── 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 | }
--------------------------------------------------------------------------------