Skip to content

Instantly share code, notes, and snippets.

@lookingcloudy
Last active September 13, 2022 00:17
Show Gist options
  • Save lookingcloudy/e23116430f1d757b6642 to your computer and use it in GitHub Desktop.
Save lookingcloudy/e23116430f1d757b6642 to your computer and use it in GitHub Desktop.
Moteino SMS Gateway
/**
* 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);
});
{
"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