first working version
authorebelcrom <ebelcrom@gmail.com>
Sun, 9 Feb 2020 18:48:17 +0000 (19:48 +0100)
committerebelcrom <ebelcrom@gmail.com>
Sun, 9 Feb 2020 18:48:17 +0000 (19:48 +0100)
15 files changed:
app.js
lib/db.js [new file with mode: 0644]
lib/dblib.js [deleted file]
lib/gpio.js [new file with mode: 0644]
package-lock.json
package.json
routes/v1/control.js
routes/v1/events.js
routes/v1/notification.js
routes/v1/status.js
routes/v1/test/control.js
routes/v1/test/events.js
routes/v1/test/notification.js
routes/v1/test/settings.js
routes/v1/test/status.js

diff --git a/app.js b/app.js
index 22038bf252fc72bd4ae1a79844373613d6bc857e..38455b26ddae4228aeb33fef43003ab035a7d6d4 100644 (file)
--- a/app.js
+++ b/app.js
@@ -11,7 +11,7 @@ const indexRouter = require('./routes/index');
 
 // routes
 const prodStatus = require('./routes/' + ver + '/status');
-const prodEvents = require('./routes/' + ver + '/events');
+const prodEvents = require('./routes/' + ver + '/events').router;
 const prodControl = require('./routes/' + ver + '/control');
 const prodNotification = require('./routes/' + ver + '/notification');
 const testStatus = require('./routes/' + ver + '/test/status');
@@ -65,14 +65,4 @@ app.use('/' + ver + '/test/settings', testSettings);
 // api-docs
 app.use('/' + ver + '/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
 
-/*
-app.use((err, req, res, next) => {
-log.error(err);
-       res.status(err.status).json({
-               message: err.message,
-               errors: err.errors,
-       });
-});
-*/
-
 module.exports = app;
diff --git a/lib/db.js b/lib/db.js
new file mode 100644 (file)
index 0000000..8d35a61
--- /dev/null
+++ b/lib/db.js
@@ -0,0 +1,253 @@
+const MongoClient = require('mongodb').MongoClient;
+const log = require('./logger')(__filename.slice(__dirname.length + 1));
+const url = 'mongodb://localhost:27017';
+const name = 'garnod';
+var db = null;
+
+const msg = {
+  ok: 'ok',
+  dbError: 'dbError',
+  keyMismatch: 'keyMismatch'
+};
+
+function connect() {
+  // connect to mongodb/mydb once, auto_reconnect is set by default
+  if (db === null) {
+    MongoClient.connect(url, function(err, connection) {
+      if (err) {
+        log.error('Connection to database failed', JSON.stringify(err));
+      } else {
+        log.info('Connection to database opened');
+        db = connection;
+        // handle irreversible connection lost
+        db.on('close', function() {
+          log.error('Connection to DB permanently lost');
+          process.exit(1);
+        });
+      }
+    });
+  }
+}
+
+// connect to DB immediately
+connect();
+
+function setSettings(data, callback) {
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // save settings
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('settings');
+      log.debug('Update settings:', JSON.stringify(data.settings));
+      settings.updateOne({ _id: 1 }, { $set: data.settings }, { upsert: true }, (err, res) => {
+        if (err) {
+          log.error('Error on updating settings', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Update settings done');
+        resolve(msg.ok);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on updating settings', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
+}
+
+function setNotification(data, callback) {
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // save notification data
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('notifications');
+      log.debug('Update notifications:', JSON.stringify(data.notification));
+      settings.updateOne({ _id: 1 }, { $set: data.notification }, { upsert: true }, (err, res) => {
+        if (err) {
+          log.error('Error on updating notification', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Update notification done');
+        resolve(msg.ok);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on updating notification', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
+}
+
+function getSettings(data, callback) {
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // get settings
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('settings');
+      log.debug('Search for settings object');
+      settings.findOne({ _id: 1 }, {}, (err, res) => {
+        if (err) {
+          log.error('Error on getting settings', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Get settings done:', JSON.stringify(res));
+        resolve(res);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on getting settings', err);
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
+}
+
+function getNotification(data, callback) {
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // get notification
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('notifications');
+      log.debug('Search for notification object');
+      settings.findOne({ _id: 1 }, {}, (err, res) => {
+        if (err) {
+          log.error('Error on getting notification', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Get notification done:', JSON.stringify(res));
+        resolve(res);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on getting notification', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
+}
+
+module.exports = {
+  msg,
+  setSettings,
+  getSettings,
+  setNotification,
+  getNotification
+}
diff --git a/lib/dblib.js b/lib/dblib.js
deleted file mode 100644 (file)
index 8d35a61..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-const MongoClient = require('mongodb').MongoClient;
-const log = require('./logger')(__filename.slice(__dirname.length + 1));
-const url = 'mongodb://localhost:27017';
-const name = 'garnod';
-var db = null;
-
-const msg = {
-  ok: 'ok',
-  dbError: 'dbError',
-  keyMismatch: 'keyMismatch'
-};
-
-function connect() {
-  // connect to mongodb/mydb once, auto_reconnect is set by default
-  if (db === null) {
-    MongoClient.connect(url, function(err, connection) {
-      if (err) {
-        log.error('Connection to database failed', JSON.stringify(err));
-      } else {
-        log.info('Connection to database opened');
-        db = connection;
-        // handle irreversible connection lost
-        db.on('close', function() {
-          log.error('Connection to DB permanently lost');
-          process.exit(1);
-        });
-      }
-    });
-  }
-}
-
-// connect to DB immediately
-connect();
-
-function setSettings(data, callback) {
-  // resolve API key
-  new Promise((resolve, reject) => {
-    var keys = db.db(name).collection('keys');
-    log.debug('Search for API key, area', data.area);
-    keys.findOne({ area: data.area }, {}, (err, res) => {
-      if (err) {
-        log.error('Error on searching for API key', JSON.stringify(err));
-        return reject(msg.dbError);
-      }
-      if (res === null) {
-        log.warn('No such key area found in DB');
-        return reject(msg.dbError);
-      }
-      if (res.key !== data.key) {
-        log.info('API key doesn\'t match');
-        return reject(msg.keyMismatch);
-      }
-      log.debug('API key is valid');
-      resolve(msg.ok);
-    })
-  })
-  .catch (err => {
-    log.error('Error on searching for API key', JSON.stringify(err));
-    return callback(err, null);
-  })
-
-  // save settings
-  .then(res => {
-    return new Promise((resolve, reject) => {
-      var settings = db.db(name).collection('settings');
-      log.debug('Update settings:', JSON.stringify(data.settings));
-      settings.updateOne({ _id: 1 }, { $set: data.settings }, { upsert: true }, (err, res) => {
-        if (err) {
-          log.error('Error on updating settings', JSON.stringify(err));
-          return reject(msg.dbError);
-        }
-        log.debug('Update settings done');
-        resolve(msg.ok);
-      });
-    });
-  })
-  .catch (err => {
-    log.error('Error on updating settings', JSON.stringify(err));
-    return callback(err, null);
-  })
-
-  // return result
-  .then(res => {
-    return callback(null, res);
-  });
-}
-
-function setNotification(data, callback) {
-  // resolve API key
-  new Promise((resolve, reject) => {
-    var keys = db.db(name).collection('keys');
-    log.debug('Search for API key, area', data.area);
-    keys.findOne({ area: data.area }, {}, (err, res) => {
-      if (err) {
-        log.error('Error on searching for API key', JSON.stringify(err));
-        return reject(msg.dbError);
-      }
-      if (res === null) {
-        log.warn('No such key area found in DB');
-        return reject(msg.dbError);
-      }
-      if (res.key !== data.key) {
-        log.info('API key doesn\'t match');
-        return reject(msg.keyMismatch);
-      }
-      log.debug('API key is valid');
-      resolve(msg.ok);
-    })
-  })
-  .catch (err => {
-    log.error('Error on searching for API key', JSON.stringify(err));
-    return callback(err, null);
-  })
-
-  // save notification data
-  .then(res => {
-    return new Promise((resolve, reject) => {
-      var settings = db.db(name).collection('notifications');
-      log.debug('Update notifications:', JSON.stringify(data.notification));
-      settings.updateOne({ _id: 1 }, { $set: data.notification }, { upsert: true }, (err, res) => {
-        if (err) {
-          log.error('Error on updating notification', JSON.stringify(err));
-          return reject(msg.dbError);
-        }
-        log.debug('Update notification done');
-        resolve(msg.ok);
-      });
-    });
-  })
-  .catch (err => {
-    log.error('Error on updating notification', JSON.stringify(err));
-    return callback(err, null);
-  })
-
-  // return result
-  .then(res => {
-    return callback(null, res);
-  });
-}
-
-function getSettings(data, callback) {
-  // resolve API key
-  new Promise((resolve, reject) => {
-    var keys = db.db(name).collection('keys');
-    log.debug('Search for API key, area', data.area);
-    keys.findOne({ area: data.area }, {}, (err, res) => {
-      if (err) {
-        log.error('Error on searching for API key', JSON.stringify(err));
-        return reject(msg.dbError);
-      }
-      if (res === null) {
-        log.warn('No such key area found in DB');
-        return reject(msg.dbError);
-      }
-      if (res.key !== data.key) {
-        log.info('API key doesn\'t match');
-        return reject(msg.keyMismatch);
-      }
-      log.debug('API key is valid');
-      resolve(msg.ok);
-    })
-  })
-  .catch (err => {
-    log.error('Error on searching for API key', JSON.stringify(err));
-    return callback(err, null);
-  })
-
-  // get settings
-  .then(res => {
-    return new Promise((resolve, reject) => {
-      var settings = db.db(name).collection('settings');
-      log.debug('Search for settings object');
-      settings.findOne({ _id: 1 }, {}, (err, res) => {
-        if (err) {
-          log.error('Error on getting settings', JSON.stringify(err));
-          return reject(msg.dbError);
-        }
-        log.debug('Get settings done:', JSON.stringify(res));
-        resolve(res);
-      });
-    });
-  })
-  .catch (err => {
-    log.error('Error on getting settings', err);
-    return callback(err, null);
-  })
-
-  // return result
-  .then(res => {
-    return callback(null, res);
-  });
-}
-
-function getNotification(data, callback) {
-  // resolve API key
-  new Promise((resolve, reject) => {
-    var keys = db.db(name).collection('keys');
-    log.debug('Search for API key, area', data.area);
-    keys.findOne({ area: data.area }, {}, (err, res) => {
-      if (err) {
-        log.error('Error on searching for API key', JSON.stringify(err));
-        return reject(msg.dbError);
-      }
-      if (res === null) {
-        log.warn('No such key area found in DB');
-        return reject(msg.dbError);
-      }
-      if (res.key !== data.key) {
-        log.info('API key doesn\'t match');
-        return reject(msg.keyMismatch);
-      }
-      log.debug('API key is valid');
-      resolve(msg.ok);
-    })
-  })
-  .catch (err => {
-    log.error('Error on searching for API key', JSON.stringify(err));
-    return callback(err, null);
-  })
-
-  // get notification
-  .then(res => {
-    return new Promise((resolve, reject) => {
-      var settings = db.db(name).collection('notifications');
-      log.debug('Search for notification object');
-      settings.findOne({ _id: 1 }, {}, (err, res) => {
-        if (err) {
-          log.error('Error on getting notification', JSON.stringify(err));
-          return reject(msg.dbError);
-        }
-        log.debug('Get notification done:', JSON.stringify(res));
-        resolve(res);
-      });
-    });
-  })
-  .catch (err => {
-    log.error('Error on getting notification', JSON.stringify(err));
-    return callback(err, null);
-  })
-
-  // return result
-  .then(res => {
-    return callback(null, res);
-  });
-}
-
-module.exports = {
-  msg,
-  setSettings,
-  getSettings,
-  setNotification,
-  getNotification
-}
diff --git a/lib/gpio.js b/lib/gpio.js
new file mode 100644 (file)
index 0000000..ebb3cfd
--- /dev/null
@@ -0,0 +1,118 @@
+const rpio = require('rpio');
+const log = require('./logger')(__filename.slice(__dirname.length + 1));
+
+const RELAIS = 16; // GPIO 23
+const REED = 11; // GPIO 17
+const UNKNOWN = 2;
+const DELAY = 10;
+var reedState;
+var controlCallback = null;
+var notificationCallback = null;
+var apiKey = null;
+var timer = null;
+
+function init() {
+  rpio.open(RELAIS, rpio.OUTPUT);
+  rpio.write(RELAIS, rpio.LOW);
+  rpio.open(REED, rpio.INPUT);
+
+  reedState = readDebounced(REED);
+}
+
+init();
+
+function trigger() {
+  rpio.write(RELAIS, rpio.HIGH);
+  rpio.msleep(500);
+  rpio.write(RELAIS, rpio.LOW);
+
+  log.debug('relais triggered');
+}
+
+function readDebounced(pin) {
+  const state = rpio.read(pin);
+
+  rpio.msleep(1000);
+
+  if (state != rpio.read(pin)) {
+    log.debug('state unknown');
+    return UNKNOWN;
+  } else {
+    log.debug('state :', state);
+    return state;
+  }
+}
+
+function pollcb(pin)
+{
+  const state = readDebounced(pin);
+
+  if (state != UNKNOWN && reedState != UNKNOWN) {
+    if (state != reedState) {
+      log.debug('reed state changed, now:', state);
+
+      if (controlCallback != null) {
+        // handle callbacks
+        if (state == rpio.LOW) {
+          controlCallback('closed');
+          controlCallback = null;
+        } else {
+          controlCallback('open');
+          controlCallback = null;
+        }
+      }
+
+      // evaluate state
+      evaluation(state);
+    }
+    reedState = state;
+  }
+}
+
+rpio.poll(REED, pollcb);
+
+function register(callback) {
+  controlCallback = callback;
+}
+
+function read() {
+  const state = readDebounced(REED);
+
+  switch (state) {
+    case rpio.LOW:
+      return 'closed'
+      break;
+    case rpio.HIGH:
+      return 'open'
+      break;
+    case rpio.LOW:
+    default:
+      return 'unknown'
+      break;
+  }
+}
+
+function subscribe(callback, key) {
+  notificationCallback = callback;
+  apiKey = key;
+}
+
+function evaluation(state) {
+  if (notificationCallback != null) {
+    if (state == rpio.HIGH) {
+      timer = setTimeout(notificationCallback, DELAY * 1000, apiKey);
+      log.debug('Open door evaluation started');
+    } else {
+      clearTimeout(timer);
+      timer = null;
+      log.debug('Open door evaluation stopped');
+    }
+  }
+}
+
+module.exports = {
+  trigger,
+  register,
+  read,
+  subscribe,
+}
index 78be1d567996c66fd6d0ce35028f37cbfa7e1e51..3a4ead9265d1afc39cd55101c53ba4232ef2fff3 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "garnod",
-  "version": "0.0.0",
+  "version": "1.0.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
         "safe-buffer": "5.1.2"
       }
     },
+    "bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "requires": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
     "bn.js": {
       "version": "4.11.8",
       "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
+    "cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "requires": {
+        "object-assign": "^4",
+        "vary": "^1"
+      }
+    },
     "currently-unhandled": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
       "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
       "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A="
     },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
+    },
     "finalhandler": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
     },
+    "nan": {
+      "version": "2.14.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+      "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
+    },
     "negotiator": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
         "validate-npm-package-license": "^3.0.1"
       }
     },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
     "on-finished": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
       "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
     },
+    "rpio": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/rpio/-/rpio-2.1.1.tgz",
+      "integrity": "sha512-0Pluzy3s77JXxpUVE3zs25TAYIjYb3q1yOJVkK6taH/idzDohZH6whqp5owgYeawxs767nc8y9TdbAv0Q1xL7w==",
+      "requires": {
+        "bindings": "~1.5.0",
+        "nan": "~2.14.0"
+      }
+    },
     "safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
index 56ab0b70483ba48c801683a1520955f1c7427b83..3198139c71608bbfbc48bb5763c727885b05b385 100644 (file)
@@ -9,12 +9,14 @@
   "license": "GPL-2.0-or-later",
   "dependencies": {
     "cookie-parser": "~1.4.4",
+    "cors": "^2.8.5",
     "dateformat": "1.0.11",
     "debug": "~2.6.9",
     "express": "~4.16.1",
     "mongodb": "^2.2.36",
     "morgan": "~1.9.1",
     "query-string": "^6.8.3",
+    "rpio": "^2.1.1",
     "swagger-ui-express": "^4.1.2",
     "web-push": "^3.4.1",
     "winston": "1.1.2",
index 8cc3399de46a737a53cb1aab2fc5fb64203406ae..8b5285db9b6cfda686b1b26461c83c214165603e 100644 (file)
@@ -1,9 +1,80 @@
-var express = require('express');
-var router = express.Router();
+const express = require('express');
+const log = require('./../../lib/logger')(__filename.slice(__dirname.length + 1));
+const qStr = require('query-string');
+const router = express.Router();
+const db = require('./../../lib/db');
+const ev = require('./events');
+const gpio = require('./../../lib/gpio');
 
-/* GET  */
-router.get('/', function(req, res, next) {
-  res.status(500).send;
+const events = ev.events;
+var timer = null;
+
+/* Disables caching in client */
+function nocache(req, res, next) {
+  res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
+  res.header('Expires', '-1');
+  res.header('Pragma', 'no-cache');
+  next();
+}
+
+/* POST /vN/control */
+router.post('/', nocache, function(req, res, next) {
+  // check header
+  if (typeof req.header('X-API-Key') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
+
+  // check query parameter
+  if (typeof req.query.command != 'undefined') {
+    if (typeof req.query.command == 'string') {
+      switch (req.query.command) {
+        case 'move':
+          break;
+        default:
+          log.info('Value of command query parameter unknown');
+          res.status(400).send();
+          return;
+      }
+    }
+  }
+
+  // read settings
+  db.getSettings({
+      area: 'prod',
+      key: req.header('X-API-Key')
+    }, (err, data) => {
+    if (err) {
+      switch (err) {
+        case db.msg.dbError:
+          log.info('Server error response');
+          res.status(500).send();
+          break;
+        case db.msg.keyMismatch:
+          log.info('Unauthorized access');
+          res.status(401).send();
+          break;
+        default:
+          log.error('Error result unexpected');
+          res.status(500).send();
+          break;
+      }
+    } else {
+      // register callback
+      gpio.register(onStateChanged);
+      gpio.trigger();
+      log.debug('Door closing triggered and state change event subscribed');
+      // send response
+      res.status(200).send();
+    }
+  });
 });
 
+function onStateChanged(state) {
+  events.emit('stateChanged', state);
+}
+
 module.exports = router;
index 8cc3399de46a737a53cb1aab2fc5fb64203406ae..cfcd85d48c218190f675ef3c8e3e71a2c03f666f 100644 (file)
@@ -1,9 +1,120 @@
-var express = require('express');
-var router = express.Router();
+const express = require('express');
+const log = require('./../../lib/logger')(__filename.slice(__dirname.length + 1));
+const qStr = require('query-string');
+const router = express.Router();
+const db = require('./../../lib/db');
+const EventEmitter = require('events');
+const fs = require('fs');
 
-/* GET  */
-router.get('/', function(req, res, next) {
-  res.status(500).send;
+const events = new EventEmitter();
+var response = null;
+var request = null;
+var timer = null;
+const delayMin = -1;
+const delayMax = 300;
+
+function nocache(req, res, next) {
+  res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
+  res.header('Expires', '-1');
+  res.header('Pragma', 'no-cache');
+  next();
+}
+
+/* GET /vN/events */
+router.get('/', nocache, function(req, res, next) {
+  // check header
+  if (typeof req.header('X-API-Key') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
+
+  // TODO: dequeue?
+
+  // check query parameter
+  var timeout = 30;
+  if (typeof req.query.timeout != 'undefined') {
+    var delay = parseInt(req.query.timeout);
+    if (!isNaN(delay)) {
+      if (delay < delayMin || delay > delayMax) {
+        log.info('Value of timeout query parameter not in range');
+        res.status(400).send();
+        return;
+      } else {
+        timeout = delay;
+      }
+    }
+  }
+
+  // schedule resonse on timeout
+  response = res;
+  request = req;
+  if (timer === null && timeout !== delayMin) {
+    timer = setTimeout(processTimeout, timeout * 1000);
+    log.debug('Response timeout scheduled');
+  } else {
+    log.todo('not implemented');
+  }
 });
 
-module.exports = router;
+events.on('stateChanged', (state) => {
+  log.debug('Event ready for sending');
+  if (timer !== null) {
+    clearTimeout(timer);
+    timer = null;
+  }
+
+  // read settings
+  log.debug('Got state:', state);
+  var image = true;
+  if (request !== null) {
+    if (typeof request.query.image != 'undefined') {
+      if (request.query.image === 'false') {
+        image = false;
+      }
+    }
+    request = null;
+  }
+
+  // TODO: enqueue?
+
+  // response
+  if (response !== null) {
+    var content = {
+      'state': state
+    };
+    if (image) {
+      var file = null;
+      switch (state) {
+        case 'open':
+          file = fs.readFileSync(__dirname + '/../../public/images/open.jpg', 'base64');
+          break;
+        case 'closed':
+          file = fs.readFileSync(__dirname + '/../../public/images/closed.jpg', 'base64');
+          break;
+        default:
+          log.error('Unexpected status from state');
+          response.status(500).send();
+          return;
+      }
+      content['image'] = file;
+    }
+    response.json(content);
+    response = null;
+  }
+});
+
+function processTimeout() {
+  log.debug('Timeout, no events');
+  clearTimeout(timer);
+  timer = null;
+  response.status(304).end('');
+  response = null;
+}
+
+module.exports = {
+  router,
+  events
+}
index 8cc3399de46a737a53cb1aab2fc5fb64203406ae..cf2c6990bebaeaeb462f1e126481293198d8ef73 100644 (file)
@@ -1,9 +1,170 @@
-var express = require('express');
-var router = express.Router();
+const express = require('express');
+const log = require('./../../lib/logger')(__filename.slice(__dirname.length + 1));
+const qStr = require('query-string');
+const router = express.Router();
+const db = require('./../../lib/db');
+const gpio = require('./../../lib/gpio');
+const fs = require('fs');
+const webpush = require('web-push');
 
-/* GET  */
-router.get('/', function(req, res, next) {
-  res.status(500).send;
+/* Disables caching in client */
+function nocache(req, res, next) {
+  res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
+  res.header('Expires', '-1');
+  res.header('Pragma', 'no-cache');
+  next();
+}
+
+/* POST /vN/notification */
+router.post('/', nocache, function(req, res, next) {
+  // check header
+  if (typeof req.header('X-API-Key') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
+
+  // check body
+  if (typeof req.body != 'object') {
+    log.info('Request body is not JSON');
+    res.status(400).send();
+    return;
+  } else{
+    // check endpoint
+    if (typeof req.body.endpoint != 'string' ) {
+      log.info('Type of endpoint in content invalid');
+      res.status(400).send();
+      return;
+    }
+
+    // check expirationTime
+    if (req.body.expirationTime !== null) {
+      if (typeof req.body.expirationTime != 'string' ) {
+        log.info('Type of expirationTime in content invalid');
+        res.status(400).send();
+        return;
+      }
+    }
+
+    // check keys
+    if (typeof req.body.keys == 'object' ) {
+      if (typeof req.body.keys.p256dh != 'string' ) {
+        log.info('Type of keys.p256dh in content invalid');
+        res.status(400).send();
+        return;
+      }
+      if (typeof req.body.keys.auth != 'string' ) {
+        log.info('Type of keys.auth in content invalid');
+        res.status(400).send();
+        return;
+      }
+    }
+
+    // save to DB
+    db.setNotification({
+        area: 'prod',
+        key: req.header('X-API-Key'),
+        notification: req.body
+      }, (err, data) => {
+      if (err) {
+        switch (err) {
+          case db.msg.dbError:
+            log.info('Server error response');
+            res.status(500).send();
+            break;
+          case db.msg.keyMismatch:
+            log.info('Unauthorized access');
+            res.status(401).send();
+            break;
+          default:
+            log.error('Error result unexpected');
+            res.status(500).send();
+            break;
+        }
+      } else {
+        // subscribe notification
+        gpio.subscribe(onDoorOpen, req.header('X-API-Key'));
+        log.debug('Open door event subscribed');
+        res.status(200).send();
+      }
+    });
+  }
 });
 
+function onDoorOpen(key) {
+  // get settings
+  db.getNotification({
+      area: 'prod',
+      key: key
+    }, (err, data) => {
+    if (err) {
+      switch (err) {
+        case db.msg.dbError:
+          log.info('Server error response');
+          res.status(500).send();
+          break;
+        case db.msg.keyMismatch:
+          log.info('Unauthorized access');
+          res.status(401).send();
+          break;
+        default:
+          log.error('Error result unexpected');
+          res.status(500).send();
+          break;
+      }
+    } else {
+      executeNotification(data);
+    }
+  });
+}
+
+function executeNotification(notification) {
+  const pushSubscription = {
+    endpoint: notification.endpoint,
+    keys: {
+      auth: notification.keys.auth,
+      p256dh: notification.keys.p256dh
+    }
+  };
+
+  const options = {
+    gcmAPIKey: 'AAAAUtHuYco:APA91bEBTxCRGaez9_glljXAlit3PY5HMwhLSqWYMC1j-jFSp6'
+      + 'nvnNqjI42jAVFApQbM0oyAOQjCUilIovB76cwTFxyZTP96wm9n09XwiMRXJjhwiJX1hO3'
+      + '2mBB2zwK6X-w7epE1V67K',
+    vapidDetails: {
+      subject: 'mailto:ebelcrom@gmail.com',
+      publicKey: 'BLDSdGasI5sLks30brbIWvlLMFqzoxxkOs7aW_E9PDBzIO_mDs6-tvtb2U0-'
+        + 'BVFDafNd58DJgoXxdK5711FF29c',
+      privateKey: '_AFTIegzYV_l_5RYwzOCc22cpYcMUmpkA8bLbrlNq9I'
+    },
+    headers: {
+      'Urgency': 'high'
+    },
+    contentEncoding: 'aes128gcm'
+  };
+
+  const icon = 'data:image/png;base64,' +
+    fs.readFileSync(__dirname + '/../../public/images/icon.png', 'base64');
+  const payload = {
+    message: 'The garage door is open!',
+    icon: icon
+  };
+  log.debug('Paylod length:', JSON.stringify(payload).length);
+
+  const details = webpush.generateRequestDetails(pushSubscription, JSON.stringify(payload), options);
+  log.debug('Request details:', JSON.stringify(details));
+
+  webpush.sendNotification(pushSubscription, JSON.stringify(payload), options)
+  .then(data => {
+    if (data) {
+      log.info('sent, data:', JSON.stringify(data));
+    }
+  })
+  .catch(err => {
+    log.info('sent, err:', JSON.stringify(err));
+  });
+}
+
 module.exports = router;
index 8cc3399de46a737a53cb1aab2fc5fb64203406ae..d13224105b9b8e91ca448e4fff728e4f859ead2e 100644 (file)
@@ -1,9 +1,84 @@
-var express = require('express');
-var router = express.Router();
+const express = require('express');
+const log = require('./../../lib/logger')(__filename.slice(__dirname.length + 1));
+const qStr = require('query-string');
+const router = express.Router();
+const db = require('./../../lib/db');
+const fs = require('fs');
+const gpio = require('./../../lib/gpio');
 
-/* GET  */
-router.get('/', function(req, res, next) {
-  res.status(500).send;
+/* Disables caching in client */
+function nocache(req, res, next) {
+  res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
+  res.header('Expires', '-1');
+  res.header('Pragma', 'no-cache');
+  next();
+}
+
+/* GET /vN/status */
+router.get('/', nocache, function(req, res, next) {
+  // check header
+  if (typeof req.header('X-API-Key') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
+
+  // check query parameter
+  var image = true;
+  if (typeof req.query.image != 'undefined') {
+    if (req.query.image === 'false') {
+      image = false;
+    }
+  }
+
+  // read settings
+  db.getSettings({
+      area: 'prod',
+      key: req.header('X-API-Key')
+    }, (err, data) => {
+    if (err) {
+      switch (err) {
+        case db.msg.dbError:
+          log.info('Server error response');
+          res.status(500).send();
+          break;
+        case db.msg.keyMismatch:
+          log.info('Unauthorized access');
+          res.status(401).send();
+          break;
+        default:
+          log.error('Error result unexpected');
+          res.status(500).send();
+          break;
+      }
+    } else {
+      // response
+      const state = gpio.read();
+
+      var content = {
+        'state': state
+      };
+      if (image) {
+        var file = null;
+        switch (state) {
+          case 'open':
+            file = fs.readFileSync(__dirname + '/../../public/images/open.jpg', 'base64');
+            break;
+          case 'closed':
+            file = fs.readFileSync(__dirname + '/../../public/images/closed.jpg', 'base64');
+            break;
+          default:
+            log.error('Unexpected status from settings');
+            res.status(500).send();
+            return;
+        }
+        content['image'] = file;
+      }
+      res.json(content);
+    }
+  });
 });
 
 module.exports = router;
index ec700d7a97033b20f82c469b7fc35ada162d0842..4b89e6a6c0c2f573557e20b3ad9536fa6e01db67 100644 (file)
@@ -2,7 +2,7 @@ const express = require('express');
 const log = require('./../../../lib/logger')(__filename.slice(__dirname.length + 1));
 const qStr = require('query-string');
 const router = express.Router();
-const dblib = require('./../../../lib/dblib');
+const db = require('./../../../lib/db');
 const ev = require('./events');
 
 const events = ev.events;
@@ -49,17 +49,17 @@ router.post('/', nocache, function(req, res, next) {
 
 function move(req, res, next) {
   // get settings
-  dblib.getSettings({
+  db.getSettings({
       area: 'test',
       key: req.header('X-API-Key-Test')
     }, (err, data) => {
     if (err) {
       switch (err) {
-        case dblib.msg.dbError:
+        case db.msg.dbError:
           log.info('Server error response');
           res.status(500).send();
           break;
-        case dblib.msg.keyMismatch:
+        case db.msg.keyMismatch:
           log.info('Unauthorized access');
           res.status(401).send();
           break;
index bd31a75b69c180db5c509e9761c36cf4520ee57e..45362e51ada031ab6abd068e5aea4b4274d6a335 100644 (file)
@@ -2,7 +2,7 @@ const express = require('express');
 const log = require('./../../../lib/logger')(__filename.slice(__dirname.length + 1));
 const qStr = require('query-string');
 const router = express.Router();
-const dblib = require('./../../../lib/dblib');
+const db = require('./../../../lib/db');
 const EventEmitter = require('events');
 const fs = require('fs');
 
index c2db2e0318f2546c8749fd8de8a1153b8b89012f..8295a9647703a8d56569ffa230bd69930dfaac3a 100644 (file)
@@ -2,9 +2,9 @@ const express = require('express');
 const log = require('./../../../lib/logger')(__filename.slice(__dirname.length + 1));
 const qStr = require('query-string');
 const router = express.Router();
-const dblib = require('./../../../lib/dblib');
+const db = require('./../../../lib/db');
 
-/* Disables caching in client */ 
+/* Disables caching in client */
 function nocache(req, res, next) {
   res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
   res.header('Expires', '-1');
@@ -12,7 +12,7 @@ function nocache(req, res, next) {
   next();
 }
 
-/* POST /vN/test/notification */
+/* POST /vN/notification */
 router.post('/', nocache, function(req, res, next) {
   // check header
   if (typeof req.header('X-API-Key-Test') === 'undefined') {
@@ -60,18 +60,18 @@ router.post('/', nocache, function(req, res, next) {
     }
 
     // save to DB
-    dblib.setNotification({
+    db.setNotification({
         area: 'test',
         key: req.header('X-API-Key-Test'),
         notification: req.body
       }, (err, data) => {
       if (err) {
         switch (err) {
-          case dblib.msg.dbError:
+          case db.msg.dbError:
             log.info('Server error response');
             res.status(500).send();
             break;
-          case dblib.msg.keyMismatch:
+          case db.msg.keyMismatch:
             log.info('Unauthorized access');
             res.status(401).send();
             break;
index 423c1f66077f02d493538d644746e1e785c89f00..27e3f13b74f30f37dc05c5a8a1c4d68f7a311d6b 100644 (file)
@@ -2,7 +2,7 @@ const express = require('express');
 const log = require('./../../../lib/logger')(__filename.slice(__dirname.length + 1));
 const qStr = require('query-string');
 const router = express.Router();
-const dblib = require('./../../../lib/dblib');
+const db = require('./../../../lib/db');
 const fs = require('fs');
 const webpush = require('web-push');
 
@@ -129,18 +129,18 @@ router.post('/', function(req, res, next) {
     }
 
     // save to DB
-    dblib.setSettings({
+    db.setSettings({
         area: 'test',
         key: req.header('X-API-Key-Test'),
         settings: settings
       }, (err, data) => {
       if (err) {
         switch (err) {
-          case dblib.msg.dbError:
+          case db.msg.dbError:
             log.info('Server error response');
             res.status(500).send();
             break;
-          case dblib.msg.keyMismatch:
+          case db.msg.keyMismatch:
             log.info('Unauthorized access');
             res.status(401).send();
             break;
@@ -166,17 +166,17 @@ router.post('/', function(req, res, next) {
 
 function notify(req, res, next, delay) {
   // get settings
-  dblib.getNotification({
+  db.getNotification({
       area: 'test',
       key: req.header('X-API-Key-Test')
     }, (err, data) => {
     if (err) {
       switch (err) {
-        case dblib.msg.dbError:
+        case db.msg.dbError:
           log.info('Server error response');
           res.status(500).send();
           break;
-        case dblib.msg.keyMismatch:
+        case db.msg.keyMismatch:
           log.info('Unauthorized access');
           res.status(401).send();
           break;
@@ -206,16 +206,6 @@ function executeNotification(notification) {
   clearTimeout(timer);
   timer = null;
 
-  // web push
-  webpush.setGCMAPIKey('AAAAUtHuYco:APA91bEBTxCRGaez9_glljXAlit3PY5HMwhLSqWYMC1j-jFSp6nvnNqj' +
-    'I42jAVFApQbM0oyAOQjCUilIovB76cwTFxyZTP96wm9n09XwiMRXJjhwiJX1hO32mBB2zwK6X-w7epE1V67K');
-  webpush.setVapidDetails(
-    'mailto:ebelcrom@gmail.com',
-    'BLDSdGasI5sLks30brbIWvlLMFqzoxxkOs7aW_E9PDBzIO_mDs6-tvtb2U0-BVFDafNd58DJgoXxdK5711FF29c',
-    '_AFTIegzYV_l_5RYwzOCc22cpYcMUmpkA8bLbrlNq9I'
-  );
-
-  log.debug('Notification data:', JSON.stringify(notification))
   const pushSubscription = {
     endpoint: notification.endpoint,
     keys: {
@@ -224,6 +214,22 @@ function executeNotification(notification) {
     }
   };
 
+  const options = {
+    gcmAPIKey: 'AAAAUtHuYco:APA91bEBTxCRGaez9_glljXAlit3PY5HMwhLSqWYMC1j-jFSp6'
+      + 'nvnNqjI42jAVFApQbM0oyAOQjCUilIovB76cwTFxyZTP96wm9n09XwiMRXJjhwiJX1hO3'
+      + '2mBB2zwK6X-w7epE1V67K',
+    vapidDetails: {
+      subject: 'mailto:ebelcrom@gmail.com',
+      publicKey: 'BLDSdGasI5sLks30brbIWvlLMFqzoxxkOs7aW_E9PDBzIO_mDs6-tvtb2U0-'
+        + 'BVFDafNd58DJgoXxdK5711FF29c',
+      privateKey: '_AFTIegzYV_l_5RYwzOCc22cpYcMUmpkA8bLbrlNq9I'
+    },
+    headers: {
+      'Urgency': 'high'
+    },
+    contentEncoding: 'aes128gcm'
+  };
+
   const icon = 'data:image/png;base64,' +
     fs.readFileSync(__dirname + '/../../../public/images/icon.png', 'base64');
   const payload = {
@@ -232,7 +238,10 @@ function executeNotification(notification) {
   };
   log.debug('Paylod length:', JSON.stringify(payload).length);
 
-  webpush.sendNotification(pushSubscription, JSON.stringify(payload))
+  const details = webpush.generateRequestDetails(pushSubscription, JSON.stringify(payload), options);
+  log.debug('Request details:', JSON.stringify(details));
+
+  webpush.sendNotification(pushSubscription, JSON.stringify(payload), options)
   .then(data => {
     if (data) {
       log.info('sent, data:', JSON.stringify(data));
index 3edfb91ac1bd1b6c83c1cf16d296a96bf31d07c3..a4b71ed07c3290e5f8be8df56db31c2af324a0dd 100644 (file)
@@ -2,10 +2,10 @@ const express = require('express');
 const log = require('./../../../lib/logger')(__filename.slice(__dirname.length + 1));
 const qStr = require('query-string');
 const router = express.Router();
-const dblib = require('./../../../lib/dblib');
+const db = require('./../../../lib/db');
 const fs = require('fs');
 
-/* Disables caching in client */ 
+/* Disables caching in client */
 function nocache(req, res, next) {
   res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
   res.header('Expires', '-1');
@@ -33,17 +33,17 @@ router.get('/', nocache, function(req, res, next) {
   }
 
   // read settings
-  dblib.getSettings({
+  db.getSettings({
       area: 'test',
       key: req.header('X-API-Key-Test')
     }, (err, data) => {
     if (err) {
       switch (err) {
-        case dblib.msg.dbError:
+        case db.msg.dbError:
           log.info('Server error response');
           res.status(500).send();
           break;
-        case dblib.msg.keyMismatch:
+        case db.msg.keyMismatch:
           log.info('Unauthorized access');
           res.status(401).send();
           break;