Last active
September 13, 2022 00:17
-
-
Save lookingcloudy/e23116430f1d757b6642 to your computer and use it in GitHub Desktop.
Moteino SMS Gateway
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by brad on 10/24/15. | |
*/ | |
/* | |
* USER DEFINED SETTINGS | |
*/ | |
var twilio_sid = 'your-sid-here'; | |
var twilio_auth = 'your-key-here'; | |
var socketURL = 'http://10.0.0.8:8080'; // assumes this runs on the gateway Pi | |
var consoleLogDateFormat = 'mm-dd-yy_HH:MM:ss.l'; | |
var garageNodeID = 11; | |
var authorizedSubscribers = ['+15556667777', '+15556668888']; // list of phone numbers authorized to interact with me | |
var twilioNumber = '+12223334444'; // your number here | |
var listenPort = 8081; // the port for listening for webhook request from twilio | |
var openAlertTimeout = 45; // number of minutes to wait before alerting | |
// subscribers that the door has been left open | |
/* | |
* DEPENDENCIES | |
*/ | |
var http = require('http'); | |
var express = require('express')(); | |
var twilio = require('twilio')(twilio_sid, twilio_auth); | |
var socket = require('socket.io-client')(socketURL); | |
var bodyParser = require('body-parser'); // used by express | |
var _ = require('lodash'); | |
require("console-stamp")(console, consoleLogDateFormat); // add timestamp to console log | |
/* | |
* MODULE VARIABLES | |
*/ | |
var timers=[]; // array of timer objects | |
var lastStatus = ''; // keep track of the status | |
var lastStatusTimer; // timerObject reference | |
var kvStore = {}; // key, value store | |
/* | |
* Startup -> Request the current status upon startup | |
*/ | |
doorUpdate('STS'); | |
/* | |
* SOCKET EVENT HANDLERS | |
*/ | |
socket.on('connect', function () { | |
console.log('Socket connected!') | |
}); | |
socket.on('connect_error', function (err) { | |
console.warn('Error connecting: ', err) | |
}); | |
socket.on('connect_timeout', function () { | |
console.warn('Timeout trying to connect') | |
}); | |
socket.on('UPDATENODE', function (entry) { | |
var status = {}; | |
var interestingStatus = [/^open$/i, /^closed$/i]; | |
status.id = entry._id; | |
status.label = entry.label; | |
status.status = entry.metrics.Status.value; | |
status.timestamp = entry.updated; | |
lastStatus = status.status; | |
console.log('Received update:', status); | |
// if the door stays open...send an alert to all subscribers | |
if (lastStatus !== 'CLOSED') { | |
if (lastStatusTimer) { // should never fire, but just in case | |
clearInterval(lastStatusTimer) | |
} | |
lastStatusTimer = setInterval(function () { | |
smsSendUpdate('Someone left the garage door open for more than ' + openAlertTimeout + ' minutes!') | |
}, openAlertTimeout * 60000) | |
} else if (lastStatusTimer) { | |
clearInterval(lastStatusTimer) | |
} | |
// send status update | |
interestingStatus.forEach(function (s) { // only for open or closed | |
if (s.test(status.status)) { | |
_.forEach(kvStore, function (v, k) { // only for listeners | |
if (v == 'listen') { | |
smsSendUpdate(status.status, k); // send the update | |
setValue(k, ''); // stop listening | |
} | |
}) | |
} | |
}); | |
}); | |
/* | |
* CONFIGURE EXPRESS WEBHOOK LISTENER | |
*/ | |
express.use(bodyParser.urlencoded({ extended: true })); | |
// listen on /sms for incoming messages from Twilio | |
express.post('/sms', function(req, res) { | |
var twiml = new require('twilio').TwimlResponse(); | |
var commands = [ | |
// open [n1] [n2] n1 means open in n1 minutes, n2 means close in n2 minutes | |
// can open then close in 1 command | |
{match: /open(\s*\d+)?(\s*\d+)?/i, command: 'OPN', command2: 'CLS'}, | |
// close in n1 minutes | |
{match: /close(\s*\d+)?/i, command: 'CLS'} | |
]; | |
var verb = req.body.Body.toLowerCase(); | |
var MessageSid = req.body.MessageSid; | |
var AccountSid = req.body.AccountSid; | |
var from = req.body.From; | |
var to = req.body.To; | |
console.log('Message recieved from Twilio: From: ', from, ' To: ', to, ' Message: ', verb); | |
// verify the to number is valid | |
if (to != twilioNumber) { | |
console.error('Received to an invalid number: ', to); | |
return | |
} | |
// verify the from number is valid | |
if (!_.includes(authorizedSubscribers, from)) { | |
console.error('Received message from unauthorized subscriber: ', from); | |
twiml.message('Sorry - who are you? We are not friends ;('); | |
res.writeHead(200, {'Content-Type': 'text/xml'}); | |
res.end(twiml.toString()); | |
return | |
} | |
var command = _.find(commands, function (c) { | |
return c['match'].test(verb) | |
}); | |
if (command) { | |
var match = _.dropRight(command['match'].exec(verb), 0); // chop off the last element | |
var delay1 = match.length > 1 ? match[1] : 0; | |
var delay2 = match.length > 2 ? match[2] : undefined; | |
// the subscriber issued a command...provide a status update for just that subscriber | |
setValue(from, ''); | |
timers = clearTimers(timers); // clear any pending delayed function calls...a new command resets old commands | |
tobj = setTimeout(function () { | |
setValue(from, 'listen'); // listen for the next update | |
doorUpdate(command.command); | |
}, delay1 * 60000); | |
timers.push(tobj); | |
if (command['command2'] && delay2) { | |
tobj = setTimeout(function () { | |
setValue(from, 'listen'); // listen for the next update | |
doorUpdate(command.command2) | |
}, delay2 * 60000); | |
timers.push(tobj); | |
} | |
} | |
res.writeHead(200, {'Content-Type': 'text/xml'}); | |
res.end(twiml.toString()); | |
}); | |
/* | |
* MAIN APPLICATION HELPER FUNCTIONS | |
*/ | |
function clearTimers (timers) { | |
_.forEach(timers, function (timer) { | |
clearTimeout(timer) | |
}); | |
return [] | |
} | |
function smsSendUpdate(txt, subscriber) { | |
var message = {}; | |
var subs = subscriber ? [subscriber] : authorizedSubscribers; | |
subs.forEach(function (subscriber) { | |
var timestamp; | |
message.to = subscriber; | |
message.from = twilioNumber; | |
message.body = txt; | |
twilio.sendMessage(message, function (err, data) { | |
if (err) { | |
console.log('Error sending message to twilio: ', err, data); | |
} else { | |
console.log('Update sent to twilio subscriber: ', subscriber) | |
} | |
}); | |
}); | |
} | |
function doorUpdate (verb) { | |
var message = {nodeId:garageNodeID, action:verb, nodeType:'GarageMote', cKey:'refresh', sKey:'Refresh'}; | |
console.log('Sending request to door', message); | |
socket.emit('CONTROLCLICK', message) | |
} | |
/* | |
* Key, Value cookie storage | |
*/ | |
function getValue (key) { | |
return kvStore[key] | |
} | |
function setValue (key, val) { | |
kvStore[key] = val; | |
return kvStore[key] | |
} | |
http.createServer(express).listen(listenPort, function () { | |
console.log("Express server listening on port ", listenPort); | |
}); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "twillio", | |
"version": "1.0.0", | |
"description": "", | |
"main": "moteinosms.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"author": "", | |
"license": "ISC", | |
"dependencies": { | |
"body-parser": "^1.14.1", | |
"console-stamp": "^0.2.0", | |
"express": "^4.13.3", | |
"lodash": "^3.10.1", | |
"socket.io-client": "^1.3.7", | |
"twilio": "^2.5.1" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment