x10-remote
Version:
X10 Home Automation Server
314 lines (269 loc) • 10.5 kB
JavaScript
/*
Author: Eric Conner
Project: X10-Remote
Date: 01-14-2015
Version: 1.0
File: X10-Remote.js
Copyright (C) 2015, Eric Conner Apps. All right Reserved.
*/
var SerialPort = require('serialport').SerialPort;
var url = require('url');
var server = require('./lib/server');
var router = require('./lib/router');
// Change API Key to something unique.
var API_KEY = 'changeMe';
// Change Serial port to match the PLM on your machine.
var SERIAL_PORT_NAME = '/dev/ttyUSB0';
// Serial port settings
var sp = new SerialPort(SERIAL_PORT_NAME, {
baudrate: 19200,
databits: 8,
stopbits: 1,
parity: 0,
flowcontrol: 0
});
// Add X10 Modules to this list. The index number must be unique.
// Name: Module name to display on webpage.
// House: X10 Module House Code.
// Unit: X10 Module Unit Code.
var MODULES = {
0: {name:'Livingroom Lamp', house:'A', unit:'1'},
1: {name:'Bedroom Lamp', house:'B', unit:'2'},
2: {name:'Kitchen Light', house:'C', unit:'3'},
3: {name:'Porch Light', house:'D', unit:'4'},
4: {name:'Garage Door', house:'E', unit:'5'},
5: {name:'Humidifier', house:'F', unit:'6'}
};
console.log('Server is running - ' + getDate() + ' - ' + getTime());
function status(response, request) {
if (!_validApiKey(response, request)) {
_contentUnauthorized(response, request);
return;
}
_contentControl(response, request);
}
function webActivate(response, request) {
if (!_validApiKey(response, request)) {
_contentUnauthorized(response, request);
return;
}
var parsedRequest = url.parse(request.url, true);
var query = parsedRequest.query;
var device = -1;
var house = '';
var unit = '';
var action = '';
var actionFormated = '';
if ((query !== undefined) && (query.device !== undefined) && (query.action !== undefined)) {
device = query.device;
action = query.action.toUpperCase();
actionFormated = action.replace(/_/g, ' ');
actionFormated = toTitleCase(actionFormated);
}
if ((device < 0) || (MODULES[device] == null)) {
_contentControl(response, request);
return;
}
console.log(MODULES[device].name + ' - ' + actionFormated + ' - ' + getTime());
house = MODULES[device].house;
unit = MODULES[device].unit;
var message = getCode(house,unit,action);
// Send message 1
sp.write(message[0]);
// Wait 500ms then send message 2
setTimeout(function() {
sp.write(message[1]);
}, 500);
_contentControl(response, request);
}
function getCode(H, U, A) {
var X10_PLM_START = 0x02;
var X10_STANDARD_MESSAGE = 0x63;
var X10_MESSAGE_FLAG_UNIT = 0x00;
var X10_MESSAGE_FLAG_COMAND = 0x80;
var houseArray = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P'];
var unitArray = ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16'];
var actionArray = ['ALL_LIGHTS_OFF','STATUS_OFF','ON','PRESET_DIM','ALL_LIGHTS_ON','HAIL_ACK','BRIGHT','STATUS_ON','EXTENDED_CODE','STATUS_REQUEST','OFF','PRESET_DIM','ALL_UNITS_OFF','HAIL_REQ','DIM','EXTENDED_DATA'];
var hexValue = [0x6,0xE,0x2,0xA,0x1,0x9,0x5,0xD,0x7,0xF,0x3,0xB,0x0,0x8,0x4,0xC];
// Validate House Letter (A-P)
H = H.toUpperCase();
if (houseArray.indexOf(H) < 0) {
throw new Error('Invalid House Letter: ' + H);
} else {
H = houseArray.indexOf(H);
}
// Validate Unit Number (1-16)
if (unitArray.indexOf(U) < 0) {
throw new Error('Invalid Unit Number: ' + U);
} else {
U = unitArray.indexOf(U);
}
// Validate Action
A = A.toUpperCase();
if (actionArray.indexOf(A) < 0) {
throw new Error('Invalid Action: ' + A);
} else {
A = actionArray.indexOf(A);
}
// Get hex values and combine for X10_UNIT and X10_CODE
var X10_UNIT = (hexValue[H] << 4) + hexValue[U];
var X10_CODE = (hexValue[H] << 4) + hexValue[A];
var message1 = [];
var message2 = [];
message1.push(X10_PLM_START);
message1.push(X10_STANDARD_MESSAGE);
message1.push(X10_UNIT);
message1.push(X10_MESSAGE_FLAG_UNIT);
message2.push(X10_PLM_START);
message2.push(X10_STANDARD_MESSAGE);
message2.push(X10_CODE);
message2.push(X10_MESSAGE_FLAG_COMAND);
return [message1,message2];
}
function _validApiKey(response, request) {
var parsedRequest = url.parse(request.url, true);
var query = parsedRequest.query;
if ((query !== undefined) && (query.apiKey !== undefined)) {
return (API_KEY == query.apiKey);
}
return false;
}
function _contentUnauthorized(response, request) {
var body = '<html>'
+ '<head>'
+ '<title>Unauthorized</title>'
+ '</head>'
+ '<body>'
+ '<p><b>Unauthorized request!</b></p>'
+ '</body>'
+ '</html>';
response.writeHead(200, {
"Content-Type" : "text/html"
});
response.write(body);
response.end();
var parsedRequest = url.parse(request.url, true);
console.log('Unauthorized request!');
console.log(parsedRequest);
}
function _contentControl(response, request) {
var parsedRequest = url.parse(request.url, true);
var query = parsedRequest.query;
var apiKey;
if ((query !== undefined) && (query.apiKey !== undefined)) {
apiKey = query.apiKey;
}
response.setHeader("Cache-Control", "no-cache");
var query = (apiKey === undefined) ? "" : "?apiKey=" + apiKey;
var body = '<!doctype html>'
+ '<html>'
+ '<head>'
+ '<title>X10 Remote</title>'
+ '<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">'
+ '<meta name="apple-mobile-web-app-capable" content="yes" />'
+ '<meta name="apple-mobile-web-app-status-bar-style" content="black">'
+ '<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css" />'
+ '<script type="application/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>'
+ '<script type="application/javascript" src="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js"></script>'
+ '<script type="application/javascript">var ScrollFix = function(elem) {var startY, startTopScroll;elem = elem || document.querySelector(elem);if(!elem)return;elem.addEventListener("touchstart", function(event){startY = event.touches[0].pageY;startTopScroll = elem.scrollTop;if(startTopScroll <= 0)elem.scrollTop = 1;if(startTopScroll + elem.offsetHeight >= elem.scrollHeight)elem.scrollTop = elem.scrollHeight - elem.offsetHeight - 1;}, false);};</script>'
+ '<script type="application/javascript">var scrollable = document.getElementById("scrollable");new ScrollFix(scrollable);</script>'
+ '<script type="application/javascript">function BlockElasticScroll(event) {event.preventDefault();}function EnableElasticScroll(event) {event.stopPropagation();}</script>'
+ '<style>p {margin-top: 5px;margin-bottom: 0px;padding-left: 10px;}.scrollable {position: absolute;top: 0;left: 0;right: 0;bottom: 0;overflow-y: scroll;-webkit-overflow-scrolling: touch;-webkit-box-flex: 1;}.cust-ui-btn1 {width: 50% !important;}.cust-ui-btn2 {width: 50% !important;}.btns {display: flex;display: -webkit-flex;justify-content: center;}.alert-txt {color: red;font-weight: bold;}.on-txt {color: green;font-weight: bold;}.list-item {border-top: 1px solid #b3b3b3;}.ui-li-desc {margin-top: 0px !important;margin-bottom: 0px !important;}.ui-header {z-index: 10 !important;position: fixed !important;}.ui-listview {height: 100% !important;margin-top: 29px !important;overflow: auto !important;-webkit-overflow-scrolling: touch !important;}</style>'
+ '</head>'
+ '<body>'
+ '<div data-role="header" ontouchmove="BlockElasticScroll(event);">'
+ '<h1>X10 Remote</h1>'
+ '</div>'
+ '<div role="main" class="ui-content scrollable" id="scrollable">'
+ '<ul data-role="listview" data-inset="false">';
for (var i in MODULES) {
body += '<li class="ui-field-contain">'
+ '<p>' + MODULES[i].name + ':</p>'
+ '<div data-role="controlgroup" data-type="horizontal">'
+ '<a href="/activate' + query + '&device=' + i + '&action=ON" class="cust-ui-btn1" data-role="button" data-ajax="false" data-inline="true">On</a>'
+ '<a href="/activate' + query + '&device=' + i + '&action=OFF" class="cust-ui-btn2" data-role="button" data-ajax="false" data-inline="true" data-theme="a">Off</a>'
+ '</div>'
+ '</li>';
}
body += '<li class="ui-field-contain">'
+ '<div data-role="controlgroup" data-type="horizontal">'
+ '<a href="/activate' + query + '&device=0&action=ALL_LIGHTS_ON" class="cust-ui-btn1" data-role="button" data-ajax="false" data-inline="true">All On</a>'
+ '<a href="/activate' + query + '&device=0&action=ALL_LIGHTS_OFF" class="cust-ui-btn2" data-role="button" data-ajax="false" data-inline="true" data-theme="a">All Off</a>'
+ '</div>'
+ '</li>'
+ '</ul>'
+ '</div>'
+ '</body>'
+ '</html>';
response.writeHead(200, {
"Content-Type" : "text/html"
});
response.write(body);
response.end();
}
function toTitleCase(str) {
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
function objectLength(obj) {
var result = 0;
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
result++;
}
}
return result;
}
function getDate(L) {
var T = new Date();
var D = T.getDate();
var M = T.getMonth();
var Y = T.getFullYear();
var N = new Array('January','February','March','April','May','June','July','August','September','October','November','December');
var O = new Array('th','st','nd','rd','th','th','th','th','th','th');
if ((D % 100) >= 11 && (D % 100) <= 13) {
E = D + 'th';
} else {
E = D + O[D % 10];
}
if (L === 'console.log') {
M = M + 1;
M = (M < 10 ? '0' : '') + M;
D = (D < 10 ? '0' : '') + D;
return Y + '-' + M + '-' + D;
} else if (L === 'year') {
return Y;
} else if (L === 'month') {
M = M + 1;
M = (M < 10 ? '0' : '') + M;
return M;
} else {
return N[M] + ' ' + E + ', ' + Y;
}
}
function getTime(Hour, Min) {
var D = new Date();
var H12 = 0;
var T = '';
var H24 = D.getHours();
var M = D.getMinutes();
if (Hour) {
var H24 = Hour;
var M = Min;
}
M = (M < 10 ? '0' : '') + M;
if (H24 > 12) {
H12 = H24 - 12;
T = 'pm';
} else if (H24 == 00) {
H12 = 12;
T = 'am';
} else {
H12 = H24;
T = 'am';
}
return H12 + ':' + M + T;
}
var handle = {};
handle["/"] = status;
handle["/activate"] = webActivate;
server.start(router.route, handle);