UNPKG

x10-remote

Version:
314 lines (269 loc) 10.5 kB
/* 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);