├── modules ├── bmwi │ └── package.json ├── hue │ └── package.json ├── ical │ ├── package.json │ └── ical.js ├── isy │ ├── package.json │ └── isy.js ├── leap │ ├── package.json │ └── leap.js ├── rdio │ ├── package.json │ └── rdio.js ├── roku │ ├── package.json │ ├── README.md │ └── roku.js ├── room │ ├── package.json │ └── room.js ├── rss │ ├── package.json │ └── rss.js ├── sun │ ├── package.json │ └── sun.js ├── talk │ ├── package.json │ └── talk.js ├── time │ ├── package.json │ └── time.js ├── alexa │ ├── package.json │ └── lighting_api_lambda.js ├── asana │ ├── package.json │ └── asana.js ├── dummy │ ├── package.json │ └── dummy.js ├── gopro │ ├── package.json │ ├── autoexec.ash │ ├── for_sd_card │ │ └── autoexec.ash │ └── gopro.js ├── ifttt │ ├── package.json │ └── ifttt.js ├── nest_dev │ ├── package.json │ └── nest.js ├── phone │ ├── package.json │ └── phone.js ├── snmp_dev │ ├── package.json │ └── snmp.js ├── state │ ├── package.json │ └── state.js ├── timer │ ├── package.json │ └── timer.js ├── voice │ └── package.json ├── ecobee │ ├── package.json │ └── ecobee.js ├── fitbit │ ├── package.json │ └── fitbit.js ├── gmusic │ └── package.json ├── insteon │ ├── package.json │ └── insteon.js ├── jawbone │ ├── package.json │ └── jawbone.js ├── lastfm │ ├── package.json │ └── lastfm.js ├── lutron │ ├── package.json │ └── lutron.js ├── meraki │ └── package.json ├── netatmo │ ├── package.json │ └── netatmo.js ├── pebble │ ├── package.json │ └── pebble.js ├── redeye │ ├── package.json │ └── redeye.js ├── serial_dev │ ├── package.json │ └── serial.js ├── slimp3 │ ├── package.json │ ├── vfd-chars.gif │ └── slimp3.js ├── spotify │ ├── package.json │ └── spotify.js ├── storage │ ├── package.json │ └── storage.js ├── syslog │ ├── package.json │ ├── lib │ │ ├── syslog-recv.js │ │ └── syslog-messages.js │ └── syslog.js ├── airvisual │ ├── package.json │ └── airvisual.js ├── bk_hd6 │ └── package.json ├── caltrain │ ├── package.json │ └── caltrain.js ├── facebook │ ├── package.json │ └── facebook.js ├── forecast │ ├── package.json │ └── forecast.js ├── location │ ├── package.json │ └── location.js ├── powermate │ ├── package.json │ └── powermate.js ├── russound │ ├── package.json │ └── README.md ├── apn │ ├── package.json │ └── apn.js ├── mac_bridge │ ├── package.json │ └── mac_bridge.js ├── samsung_tv │ ├── package.json │ └── samsung_tv.js ├── music_search │ ├── package.json │ └── music_search_test.js ├── insteon_ip_camera │ ├── package.json │ └── insteon_ip_camera.js ├── bt-proximity │ ├── package.json │ ├── README.md │ ├── bt-proximity.js │ └── bt-proximity_experimental.js ├── denon │ ├── package.json │ └── denon.js ├── telnet │ ├── package.json │ └── telnet.js ├── web │ ├── package.json │ ├── web_express.js │ ├── web.js │ └── web_experimental.js ├── lutron-radiora2 │ └── package.json └── sonos │ └── package.json ├── examples ├── tools │ ├── lights.pyw │ ├── mediapc.ahk │ ├── speech.PIE │ ├── insteon_direct.js │ └── voice.py ├── cambridge │ ├── web │ │ ├── noise1.png │ │ ├── favicon.ico │ │ ├── sonos-pause.png │ │ ├── sonos-play.png │ │ ├── stereo-pc.png │ │ ├── stereo-ps3.png │ │ ├── stereo-roku.png │ │ ├── stereo-apple.png │ │ ├── stereo-sonos.png │ │ ├── functions.js │ │ ├── status.js │ │ ├── switch.js │ │ ├── volume.js │ │ ├── stereo.js │ │ ├── sonos.js │ │ └── index.html │ └── cambridge.js └── emerson │ ├── web │ ├── favicon.ico │ └── index.html │ ├── deploy.sh │ ├── emerson_upstart.conf │ └── emerson.js ├── .gitattributes ├── core └── package.json ├── README.md └── .gitignore /modules/bmwi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "BMWi", 3 | "main" : "./bmwi.js" 4 | } -------------------------------------------------------------------------------- /modules/hue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Hue", 3 | "main" : "./hue.js" 4 | } -------------------------------------------------------------------------------- /modules/ical/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "iCal", 3 | "main" : "./ical.js" 4 | } -------------------------------------------------------------------------------- /modules/isy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Isy", 3 | "main" : "./isy.js" 4 | } -------------------------------------------------------------------------------- /modules/leap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Leap", 3 | "main" : "./leap.js" 4 | } -------------------------------------------------------------------------------- /modules/rdio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Rdio", 3 | "main" : "./rdio.js" 4 | } -------------------------------------------------------------------------------- /modules/roku/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Roku", 3 | "main" : "./roku.js" 4 | } -------------------------------------------------------------------------------- /modules/room/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Room", 3 | "main" : "./room.js" 4 | } -------------------------------------------------------------------------------- /modules/rss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "RSS", 3 | "main" : "./rss.js" 4 | } -------------------------------------------------------------------------------- /modules/sun/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Sun", 3 | "main" : "./sun.js" 4 | } -------------------------------------------------------------------------------- /modules/talk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Talk", 3 | "main" : "./talk.js" 4 | } -------------------------------------------------------------------------------- /modules/time/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Time", 3 | "main" : "./time.js" 4 | } -------------------------------------------------------------------------------- /modules/alexa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Alexa", 3 | "main" : "./alexa.js" 4 | } -------------------------------------------------------------------------------- /modules/asana/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Asana", 3 | "main" : "./asana.js" 4 | } -------------------------------------------------------------------------------- /modules/dummy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Dummy", 3 | "main" : "./dummy.js" 4 | } -------------------------------------------------------------------------------- /modules/gopro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "GoPro", 3 | "main" : "./gopro.js" 4 | } -------------------------------------------------------------------------------- /modules/ifttt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "IFTTT", 3 | "main" : "./ifttt.js" 4 | } -------------------------------------------------------------------------------- /modules/nest_dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Nest", 3 | "main" : "./nest.js" 4 | } -------------------------------------------------------------------------------- /modules/phone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Phone", 3 | "main" : "./phone.js" 4 | } -------------------------------------------------------------------------------- /modules/snmp_dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "SNMP", 3 | "main" : "./snmp.js" 4 | } -------------------------------------------------------------------------------- /modules/state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "State", 3 | "main" : "./state.js" 4 | } -------------------------------------------------------------------------------- /modules/timer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Timer", 3 | "main" : "./timer.js" 4 | } -------------------------------------------------------------------------------- /modules/voice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Voice", 3 | "main" : "./voice.js" 4 | } -------------------------------------------------------------------------------- /modules/ecobee/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "ecobee", 3 | "main" : "./ecobee.js" 4 | } -------------------------------------------------------------------------------- /modules/fitbit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Fitbit", 3 | "main" : "./fitbit.js" 4 | } -------------------------------------------------------------------------------- /modules/gmusic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "GMusic", 3 | "main" : "./gmusic.js" 4 | } -------------------------------------------------------------------------------- /modules/insteon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Insteon", 3 | "main" : "./insteon.js" 4 | } -------------------------------------------------------------------------------- /modules/jawbone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Jawbone", 3 | "main" : "./jawbone.js" 4 | } -------------------------------------------------------------------------------- /modules/lastfm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "LastFM", 3 | "main" : "./lastfm.js" 4 | } -------------------------------------------------------------------------------- /modules/lutron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Lutron", 3 | "main" : "./lutron.js" 4 | } -------------------------------------------------------------------------------- /modules/meraki/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Meraki", 3 | "main" : "./meraki.js" 4 | } -------------------------------------------------------------------------------- /modules/netatmo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Netatmo", 3 | "main" : "./netatmo.js" 4 | } -------------------------------------------------------------------------------- /modules/pebble/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Pebble", 3 | "main" : "./pebble.js" 4 | } -------------------------------------------------------------------------------- /modules/redeye/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "RedEye", 3 | "main" : "./redeye.js" 4 | } -------------------------------------------------------------------------------- /modules/serial_dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Serial", 3 | "main" : "./serial.js" 4 | } -------------------------------------------------------------------------------- /modules/slimp3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Slimp3", 3 | "main" : "./slimp3.js" 4 | } -------------------------------------------------------------------------------- /modules/spotify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Spotify", 3 | "main" : "./spotify.js" 4 | } -------------------------------------------------------------------------------- /modules/storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Storage", 3 | "main" : "./storage.js" 4 | } -------------------------------------------------------------------------------- /modules/syslog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Syslog", 3 | "main" : "./syslog.js" 4 | } -------------------------------------------------------------------------------- /modules/airvisual/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "AirVisual", 3 | "main" : "./airvisual.js" 4 | } -------------------------------------------------------------------------------- /modules/bk_hd6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "BK_HD6", 3 | "main" : "./bk_hd6.js" 4 | } 5 | -------------------------------------------------------------------------------- /modules/caltrain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Caltrain", 3 | "main" : "./caltrain.js" 4 | } -------------------------------------------------------------------------------- /modules/facebook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Facebook", 3 | "main" : "./facebook.js" 4 | } -------------------------------------------------------------------------------- /modules/forecast/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Forecast", 3 | "main" : "./forecast.js" 4 | } -------------------------------------------------------------------------------- /modules/location/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Location", 3 | "main" : "./location.js" 4 | } -------------------------------------------------------------------------------- /modules/powermate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "PowerMate", 3 | "main" : "./powermate.js" 4 | } -------------------------------------------------------------------------------- /modules/russound/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Russound", 3 | "main" : "./russound.js" 4 | } -------------------------------------------------------------------------------- /modules/apn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Apple Push Notifications", 3 | "main" : "./apn.js" 4 | } -------------------------------------------------------------------------------- /modules/gopro/autoexec.ash: -------------------------------------------------------------------------------- 1 | sleep 4 2 | lu_util exec 'mount --bind /tmp/fuse_d/ /var/www/DCIM/' 3 | 4 | -------------------------------------------------------------------------------- /modules/mac_bridge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Mac Bridge", 3 | "main" : "./mac_bridge.js" 4 | } -------------------------------------------------------------------------------- /modules/samsung_tv/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "SamsungTV", 3 | "main" : "./samsung_tv.js" 4 | } -------------------------------------------------------------------------------- /modules/music_search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "MusicSearch", 3 | "main" : "./music_search.js" 4 | } -------------------------------------------------------------------------------- /modules/slimp3/vfd-chars.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/modules/slimp3/vfd-chars.gif -------------------------------------------------------------------------------- /examples/tools/lights.pyw: -------------------------------------------------------------------------------- 1 | import urllib2, sys 2 | urllib2.urlopen("http://10.0.1.41:8000/event/%s" % sys.argv[1]) -------------------------------------------------------------------------------- /modules/roku/README.md: -------------------------------------------------------------------------------- 1 | 2 | # russound 3 | 4 | russound module for route.io 5 | 6 | ## Example 7 | 8 | -------------------------------------------------------------------------------- /modules/russound/README.md: -------------------------------------------------------------------------------- 1 | 2 | # russound 3 | 4 | russound module for route.io 5 | 6 | ## Example 7 | 8 | -------------------------------------------------------------------------------- /examples/cambridge/web/noise1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/noise1.png -------------------------------------------------------------------------------- /examples/emerson/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/emerson/web/favicon.ico -------------------------------------------------------------------------------- /examples/cambridge/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/favicon.ico -------------------------------------------------------------------------------- /modules/insteon_ip_camera/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "InsteonIPCamera", 3 | "main" : "./insteon_ip_camera.js" 4 | } -------------------------------------------------------------------------------- /examples/cambridge/web/sonos-pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/sonos-pause.png -------------------------------------------------------------------------------- /examples/cambridge/web/sonos-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/sonos-play.png -------------------------------------------------------------------------------- /examples/cambridge/web/stereo-pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/stereo-pc.png -------------------------------------------------------------------------------- /examples/cambridge/web/stereo-ps3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/stereo-ps3.png -------------------------------------------------------------------------------- /examples/cambridge/web/stereo-roku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/stereo-roku.png -------------------------------------------------------------------------------- /examples/cambridge/web/stereo-apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/stereo-apple.png -------------------------------------------------------------------------------- /examples/cambridge/web/stereo-sonos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glenmurphy/route/HEAD/examples/cambridge/web/stereo-sonos.png -------------------------------------------------------------------------------- /modules/gopro/for_sd_card/autoexec.ash: -------------------------------------------------------------------------------- 1 | # mount SD card 2 | sleep 4 3 | lu_util exec 'mount --bind /tmp/fuse_d/ /var/www/DCIM/' 4 | -------------------------------------------------------------------------------- /examples/emerson/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")" 3 | sudo stop emerson_upstart 4 | sudo cp emerson_upstart.conf /etc/init/ 5 | sudo start emerson_upstart 6 | -------------------------------------------------------------------------------- /examples/tools/mediapc.ahk: -------------------------------------------------------------------------------- 1 | ^+i::UrlDownloadToFile, http://10.0.1.41:8000/event/MediaPCStarted, null 2 | ^+o::UrlDownloadToFile, http://10.0.1.41:8000/event/MediaPCEnded, null -------------------------------------------------------------------------------- /modules/bt-proximity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "BTProximity", 3 | "main" : "./bt-proximity.js", 4 | "dependencies" : { 5 | "pty.js" : ">= 0.2.2" 6 | } 7 | } -------------------------------------------------------------------------------- /examples/cambridge/web/functions.js: -------------------------------------------------------------------------------- 1 | function $(o) { return document.getElementById(o); } 2 | 3 | function createElement(type, className, parent, text) { 4 | var elm = document.createElement(type); 5 | elm.className = className; 6 | if (text) elm.appendChild(document.createTextNode(text)); 7 | if (parent) parent.appendChild(elm); 8 | return elm; 9 | } 10 | -------------------------------------------------------------------------------- /examples/emerson/emerson_upstart.conf: -------------------------------------------------------------------------------- 1 | # emerson.js upstart script 2 | 3 | description "Emerson Home Automator" 4 | author "glen" 5 | 6 | #start on (local-filesystems and net-device-up IFACE=eth0) 7 | 8 | start on runlevel [23] 9 | stop on runlevel 0 10 | stop on runlevel 6 11 | 12 | respawn 13 | 14 | exec sudo -u glen sh -c "/usr/local/bin/node /home/glen/emerson/examples/emerson/emerson.js >> /home/glen/emerson.log" 15 | -------------------------------------------------------------------------------- /modules/dummy/dummy.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | 4 | function Dummy(data) { 5 | this.host = data.host; 6 | }; 7 | util.inherits(Dummy, EventEmitter); 8 | 9 | Dummy.prototype.exec = function(command, data) { 10 | this.log(command); 11 | }; 12 | 13 | Dummy.prototype.log = function(data) { 14 | console.log("DUMMY LOG:" + data); 15 | this.emit("DeviceEvent", "Logged"); 16 | } 17 | 18 | exports.Dummy = Dummy; -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /modules/bt-proximity/README.md: -------------------------------------------------------------------------------- 1 | # BTProximity 2 | 3 | Bluetooth 4 proximity detection module for route.io 4 | 5 | ## Setup 6 | 7 | On a Raspberry PI, you can get Bluetooth working by buying the IOGear GBU521 and following [these instructions](http://www.ioncannon.net/linux/1570/bluetooth-4-0-le-on-raspberry-pi-with-bluez-5-x/) (these also show how to get the MAC address). 8 | 9 | ## Notes 10 | 11 | When the dbus issues are fixed so that it can work on more recent versions of NodeJS, it would be good to use [Noble](https://github.com/sandeepmistry/noble), though it's also just driving the gatttool command-line tool, so it might not be worth it. 12 | -------------------------------------------------------------------------------- /modules/denon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "route.io-denon", 3 | "version" : "0.1.0", 4 | "main" : "./denon.js", 5 | "files" : [ 6 | "denon.js" 7 | ], 8 | "description" : "Denon module for route.io.", 9 | "homepage" : "https://github.com/glenmurphy/route/tree/master/modules/denon", 10 | "repository" : { 11 | "type" : "git", 12 | "url" : "https://github.com/glenmurphy/route.git" 13 | }, 14 | "bugs" : { 15 | "url" : "http://github.com/glenmurphy/route/issues", 16 | "email" : "glen@glenmurphy.com" 17 | }, 18 | "author" : { 19 | "name" : "Glen Murphy", 20 | "email" : "glen@glenmurphy.com", 21 | "url" : "http://glenmurphy.com/" 22 | } 23 | } -------------------------------------------------------------------------------- /modules/music_search/music_search_test.js: -------------------------------------------------------------------------------- 1 | var musicSearch = require('./musicsearch.js'); 2 | musicSearch = new musicSearch({ 3 | services : ["spotify"], 4 | /*PRIVATE*/ echonest_api_key: 'EEAQAFPLA25XJ2F79' 5 | /*END_PRIVATE 6 | echonest_api_key: '' 7 | */ 8 | }); 9 | 10 | 11 | testQuery(process.argv[2]); 12 | // testQuery("Violator"); 13 | // testQuery("call me maybe"); 14 | // testQuery("cher"); 15 | 16 | function testQuery(query) { 17 | console.log("searching for", query); 18 | musicSearch.tracksForFreeformQuery(query, logTracks.bind(null, query)); 19 | } 20 | 21 | function logTracks(query, tracks) { 22 | console.log("\nQuery:", query); 23 | 24 | for (var i in tracks) { 25 | var track = tracks[i]; 26 | // console.log(track.artist_name, track.title); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/location/location.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | 4 | String.prototype.camelcase = function() { 5 | return this.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); }).replace(/ /g, ""); 6 | }; 7 | 8 | function Location(data) { 9 | this.host = data.host; 10 | this.users = {}; 11 | }; 12 | util.inherits(Location, EventEmitter); 13 | 14 | Location.prototype.exec = function(command, params) { 15 | if (command == "Update") { 16 | this.users[params.user] = params; 17 | this.emit("StateEvent", {locationUsers : this.users}); 18 | var eventDescription = params.user + " " + params.event + " " + params["location"]; 19 | this.emit("DeviceEvent", eventDescription.camelcase()); 20 | } 21 | }; 22 | 23 | module.exports = Location; -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "route.io", 3 | "version" : "0.1.0", 4 | "main" : "./route.js", 5 | "files" : [ 6 | "route.js" 7 | ], 8 | "description" : "Route links events and commands on your network-aware devices.", 9 | "homepage" : "http://route.io/", 10 | "repository" : { 11 | "type" : "git", 12 | "url" : "https://github.com/glenmurphy/route.git" 13 | }, 14 | "bugs" : { 15 | "url" : "http://github.com/glenmurphy/route/issues", 16 | "email" : "glen@glenmurphy.com" 17 | }, 18 | "author" : { 19 | "name" : "Glen Murphy", 20 | "email" : "glen@glenmurphy.com", 21 | "url" : "http://glenmurphy.com/" 22 | }, 23 | "contributors" : [{ 24 | "name" : "Nicholas Jitkoff", 25 | "email" : "nicholas@jitkoff.com", 26 | "url" : "http://blacktree.com/" 27 | }] 28 | } -------------------------------------------------------------------------------- /modules/telnet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "route.io-telnet", 3 | "version" : "0.1.0", 4 | "main" : "./telnet.js", 5 | "files" : [ 6 | "telnet.js" 7 | ], 8 | "description" : "Telnet module for route.io.", 9 | "homepage" : "https://github.com/glenmurphy/route/tree/master/modules/telnet", 10 | "repository" : { 11 | "type" : "git", 12 | "url" : "https://github.com/glenmurphy/route.git" 13 | }, 14 | "bugs" : { 15 | "url" : "http://github.com/glenmurphy/route/issues", 16 | "email" : "glen@glenmurphy.com" 17 | }, 18 | "author" : { 19 | "name" : "Glen Murphy", 20 | "email" : "glen@glenmurphy.com", 21 | "url" : "http://glenmurphy.com/" 22 | }, 23 | "contributors" : [{ 24 | "name" : "Nicholas Jitkoff", 25 | "email" : "nicholas@jitkoff.com", 26 | "url" : "http://blacktree.com/" 27 | }] 28 | } -------------------------------------------------------------------------------- /modules/redeye/redeye.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var util = require('util'); 4 | 5 | function RedEye(data) { 6 | this.host = data.host; 7 | this.executing_ = false; 8 | this.commands = data.commands ? data.commands : {}; 9 | }; 10 | util.inherits(RedEye, EventEmitter); 11 | 12 | RedEye.PORT = 82; 13 | 14 | RedEye.prototype.exec = function(command) { 15 | if (!(command in this.commands)) return; 16 | console.log("* RedEye Executing: " + command); 17 | var code = this.commands[command]; 18 | var request = http.request({ 19 | port : RedEye.PORT, 20 | host : this.host, 21 | path : "/cgi-bin/play_iph.sh?" + code + "%201" 22 | }); 23 | request.on('error', function(e) { console.log("RedEye error: " + e)}); 24 | request.end(); 25 | }; 26 | 27 | module.exports = RedEye; -------------------------------------------------------------------------------- /modules/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "route.io-web", 3 | "version" : "0.1.1", 4 | "main" : "./web.js", 5 | "files" : [ 6 | "web.js" 7 | ], 8 | "description" : "Web module for route.io.", 9 | "homepage" : "https://github.com/glenmurphy/route/tree/master/modules/web", 10 | "repository" : { 11 | "type" : "git", 12 | "url" : "https://github.com/glenmurphy/route.git" 13 | }, 14 | "dependencies" : { 15 | "socket.io" : "*" 16 | }, 17 | "bugs" : { 18 | "url" : "http://github.com/glenmurphy/route/issues", 19 | "email" : "glen@glenmurphy.com" 20 | }, 21 | "author" : { 22 | "name" : "Glen Murphy", 23 | "email" : "glen@glenmurphy.com", 24 | "url" : "http://glenmurphy.com/" 25 | }, 26 | "contributors" : [{ 27 | "name" : "Nicholas Jitkoff", 28 | "email" : "nicholas@jitkoff.com", 29 | "url" : "http://blacktree.com/" 30 | }] 31 | } -------------------------------------------------------------------------------- /modules/lutron-radiora2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "route.io-lutron-radiora2", 3 | "version" : "0.1.0", 4 | "main" : "./lutron-radiora2.js", 5 | "files" : [ 6 | "lutron-radiora2.js" 7 | ], 8 | "description" : "Lutron RadioRA2 module for route.io.", 9 | "homepage" : "https://github.com/glenmurphy/route/tree/master/modules/lutron-radiora2", 10 | "repository" : { 11 | "type" : "git", 12 | "url" : "https://github.com/glenmurphy/route.git" 13 | }, 14 | "bugs" : { 15 | "url" : "http://github.com/glenmurphy/route/issues", 16 | "email" : "glen@glenmurphy.com" 17 | }, 18 | "author" : { 19 | "name" : "Glen Murphy", 20 | "email" : "glen@glenmurphy.com", 21 | "url" : "http://glenmurphy.com/" 22 | }, 23 | "contributors" : [{ 24 | "name" : "Nicholas Jitkoff", 25 | "email" : "nicholas@jitkoff.com", 26 | "url" : "http://blacktree.com/" 27 | }] 28 | } -------------------------------------------------------------------------------- /modules/rdio/rdio.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | var http = require('http'); 5 | var xml2js = require('xml2js'); 6 | 7 | /* SPOTIFY ------------------------------------------------------------------- */ 8 | function Rdio(data) { 9 | this.bridge = data.bridge; 10 | } 11 | util.inherits(Rdio, EventEmitter); 12 | 13 | 14 | Rdio.prototype.exec = function(command, params) { 15 | console.log("* Rdio Executing: " + command); 16 | console.log(params); 17 | if (command == "Reconnect") { 18 | this.reconnect(); 19 | } else if (command == "Listen") { 20 | // this.listenTo(params.query); 21 | } else { 22 | this.bridge.sendEvent("Rdio." + command); 23 | } 24 | }; 25 | 26 | Rdio.prototype.playSource = function(track) { 27 | this.bridge.sendEvent("Rdio.ListenTo:" + track); 28 | } 29 | 30 | module.exports = Rdio; 31 | -------------------------------------------------------------------------------- /modules/sonos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "route.io-sonos", 3 | "version" : "0.1.1", 4 | "main" : "./sonos.js", 5 | "files" : [ 6 | "sonos.js" 7 | ], 8 | "description" : "Sonos module for route.io.", 9 | "homepage" : "https://github.com/glenmurphy/route/tree/master/modules/sonos", 10 | "dependencies" : { 11 | "xml2js" : "*" 12 | }, 13 | "repository" : { 14 | "type" : "git", 15 | "url" : "https://github.com/glenmurphy/route.git" 16 | }, 17 | "bugs" : { 18 | "url" : "http://github.com/glenmurphy/route/issues", 19 | "email" : "glen@glenmurphy.com" 20 | }, 21 | "author" : { 22 | "name" : "Glen Murphy", 23 | "email" : "glen@glenmurphy.com", 24 | "url" : "http://glenmurphy.com/" 25 | }, 26 | "contributors" : [{ 27 | "name" : "Nicholas Jitkoff", 28 | "email" : "nicholas@jitkoff.com", 29 | "url" : "http://blacktree.com/" 30 | },{ 31 | "name" : "Andy Warr" 32 | }] 33 | } -------------------------------------------------------------------------------- /modules/storage/storage.js: -------------------------------------------------------------------------------- 1 | var persist = require('node-persist'); 2 | 3 | var fs = require('fs'); 4 | var dir = process.env['HOME'] + "/.route.io"; 5 | 6 | if (!fs.existsSync(dir)) fs.mkdirSync(dir); 7 | console.log(dir); 8 | persist.initSync({ 9 | dir:dir, 10 | parse: function (json) { 11 | try { 12 | return JSON.parse(json); 13 | } catch (e) { 14 | console.log("! Storage error", e, "'" + json + "'" ); 15 | return undefined; 16 | } 17 | } 18 | }); 19 | 20 | function Storage(prefix) { 21 | if (prefix) this.prefix = prefix; 22 | }; 23 | 24 | Storage.prototype.setItem = function(key, value) { 25 | persist.setItem(this.realKey(key), value); 26 | } 27 | 28 | Storage.prototype.getItem = function(key) { 29 | return persist.getItem(this.realKey(key)); 30 | } 31 | 32 | Storage.prototype.realKey = function(key) { 33 | return this.prefix ? this.prefix + ":" + key : key; 34 | } 35 | 36 | module.exports = Storage; 37 | -------------------------------------------------------------------------------- /modules/room/room.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var util = require('util'); 4 | 5 | function Room(data, name) { 6 | this.id = name; 7 | this._private = function () {}; 8 | this.commands = data.commands; 9 | this.activities = data.activities; 10 | for (var key in data.values) { this[key] = data.values[key];} 11 | 12 | this.name = data.label || this.label || name; 13 | // Rooms have a private dictionary for attributes that may be cyclical 14 | Object.defineProperty( this, '_private', {value: {}, writable:true, configurable:true, enumerable:false}); 15 | } 16 | util.inherits(Room, EventEmitter); 17 | 18 | Room.prototype.exec = function(command, params) { 19 | var value = this.commands[command]; 20 | if (value) this.route.execCommands(value, params, command); 21 | }; 22 | 23 | Room.prototype.set = function(key, value) { 24 | var state = {}; 25 | state["rooms." + this.id + "." + key] = value; 26 | this[key] = value; 27 | this.emit("StateEvent", state); 28 | } 29 | 30 | module.exports = Room; -------------------------------------------------------------------------------- /modules/syslog/lib/syslog-recv.js: -------------------------------------------------------------------------------- 1 | /* 2 | * syslog-server.js 3 | * 4 | * @version 0.1.0 5 | * @author Frank Grimm (http://frankgrimm.net) 6 | * 7 | */ 8 | 9 | exports.getServer = function(bind_port, bind_ip, callback) { 10 | 11 | // bind parameters 12 | var bindip = bind_ip || undefined; 13 | var bindport = bind_port || 514; 14 | 15 | // default callback 16 | var callthis = function(msgobject) { 17 | console.log(require("sys").inspect(msgobject)); 18 | } 19 | 20 | if (typeof callback == "function") { 21 | callthis = callback; 22 | } 23 | 24 | var dgram = require("dgram"); 25 | var syslog = require("./syslog-messages"); 26 | var server = dgram.createSocket("udp4"); 27 | 28 | server.on("message", function (msg, rinfo) { 29 | syslog.decodeMessage(msg.toString('ascii'), function(receivedMsg) { 30 | // attach connection information to the received message 31 | receivedMsg.rinfo = rinfo; 32 | callthis(receivedMsg); 33 | }); 34 | }).on("listening", function () { 35 | var address = server.address(); 36 | }).bind(bindport, bindip); // bind 514:UDP on all interfaces 37 | } 38 | -------------------------------------------------------------------------------- /modules/rss/rss.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var feed = require('feed-read'); 4 | 5 | function RSS(data) { 6 | this.debug = data.debug; 7 | this.url = data.url; 8 | this.feedname = data.feedname; 9 | this.keyname = data.name; 10 | this.maxArticles = data.maxArticles; 11 | this.fetchRss(); 12 | }; 13 | util.inherits(RSS, EventEmitter); 14 | 15 | RSS.prototype.fetchRss = function() { 16 | feed(this.url, function(err, articles) { this.onRssFetched(err, articles);}.bind(this)); 17 | } 18 | 19 | RSS.prototype.onRssFetched = function(err, articles) { 20 | console.log("RSS Fetched " + this.feedname); 21 | if (articles.length > this.maxArticles) { 22 | articles = articles.slice(0, this.maxArticles); 23 | } 24 | data = {} 25 | data[this.keyname] = articles; 26 | this.emit("StateEvent", data); 27 | nextCheck = 60 * 60; 28 | if (this.debug) console.log("RSS checking in", nextCheck); 29 | setTimeout(this.fetchRss.bind(this), nextCheck * 1000); 30 | } 31 | 32 | RSS.prototype.exec = function(command, data) { 33 | console.log("RSS:" + command); 34 | }; 35 | 36 | module.exports = RSS; -------------------------------------------------------------------------------- /modules/phone/phone.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | 4 | function Phone(data) { 5 | this.numberOfCalls = 0; 6 | }; 7 | 8 | util.inherits(Phone, EventEmitter); 9 | 10 | Phone.prototype.exec = function(command, params) { 11 | if (command == "PhoneEvent") { 12 | this.handlePhoneEvent(params); 13 | } 14 | }; 15 | 16 | Phone.prototype.handlePhoneEvent = function(params) { 17 | var state = params.state; 18 | 19 | switch(state) { 20 | case "CallStarted": 21 | this.CallStarted(); 22 | break; 23 | case "CallEnded": 24 | this.CallEnded(); 25 | break; 26 | } 27 | }; 28 | 29 | Phone.prototype.CallStarted = function() { 30 | this.numberOfCalls += 1; 31 | console.log("Number of calls in progress: " + this.numberOfCalls); 32 | this.emit("DeviceEvent", "CallStarted"); 33 | }; 34 | 35 | Phone.prototype.CallEnded = function() { 36 | this.numberOfCalls -= 1; 37 | console.log("Number of calls in progress: " + this.numberOfCalls); 38 | this.emit("DeviceEvent", "CallEnded"); 39 | }; 40 | 41 | //Method for call state 42 | 43 | //Method for idle state 44 | 45 | module.exports = Phone; -------------------------------------------------------------------------------- /modules/syslog/syslog.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var net = require('net'); 4 | var syslogReceiver = require("./lib/syslog-recv"); 5 | 6 | function Syslog(data) { 7 | this.port = data.port || 514; 8 | this.matches = data.matches; 9 | this.debug = data.debug; 10 | this.debugIgnore = data.debugIgnore; 11 | this.syslogServer = syslogReceiver.getServer(this.port, null, this.handleLog.bind(this)); 12 | }; 13 | util.inherits(Syslog, EventEmitter); 14 | 15 | Syslog.prototype.handleLog = function(evt) { 16 | if (this.debug) { 17 | var ignore = false; 18 | for (var i in this.debugIgnore) { 19 | var match = evt.original.match(this.debugIgnore[i]); 20 | if (match) { 21 | ignore = true; 22 | break; 23 | } 24 | } 25 | if (!ignore) 26 | console.log(evt.original); 27 | } 28 | for (var regex in this.matches) { 29 | var match = evt.original.match(regex); 30 | if (match) { 31 | match.shift(); 32 | if (this.matches[regex]) this.emit("DeviceEvent", this.matches[regex], {match: match}); 33 | return; 34 | } 35 | } 36 | 37 | } 38 | 39 | module.exports = Syslog; -------------------------------------------------------------------------------- /modules/lastfm/lastfm.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var LastFmNode = require('lastfm').LastFmNode; 3 | var util = require('util'); 4 | 5 | function LastFM(data) { 6 | this.key = data.key; 7 | this.secret = data.secret; 8 | this.username = data.username; 9 | 10 | this.lastfm = new LastFmNode({api_key: this.key, secret: this.secret}); 11 | this.trackStream = this.lastfm.stream(this.username); 12 | 13 | this.trackStream.on('nowPlaying', this.nowPlaying.bind(this)); 14 | this.trackStream.on('stoppedPlaying', this.stoppedPlaying.bind(this)); 15 | 16 | this.trackStream.start(); 17 | 18 | this.lastPlayedTrack; 19 | } 20 | util.inherits(LastFM, EventEmitter); 21 | 22 | LastFM.prototype.nowPlaying = function(track) { 23 | var currentDate = new Date(); 24 | 25 | this.lastPlayedTrack = { 26 | album: track.album['#text'], 27 | artist: track.artist['#text'], 28 | played: currentDate.getTime(), 29 | track: track.name 30 | }; 31 | 32 | this.emit("DeviceEvent", "NowPlaying", track); 33 | }; 34 | 35 | LastFM.prototype.stoppedPlaying = function(track) { 36 | this.emit("DeviceEvent", "StoppedPlaying", track); 37 | }; 38 | 39 | module.exports = LastFM; 40 | -------------------------------------------------------------------------------- /modules/serial_dev/serial.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var SerialPort = require("serialport").SerialPort 4 | 5 | 6 | function Serial(data) { 7 | this.commands = data.commands 8 | this.port = data.port 9 | this.options = data.options; 10 | this.serialPort = new SerialPort(this.port, this.options); 11 | 12 | this.serialPort.on("open", function () { 13 | console.log('open'); 14 | this.serialPort.on('data', function(data) { 15 | console.log('data received: ' + data); 16 | }.bind(this)); 17 | var buffer = new Buffer("BEEF100500C6FF111101000100", "hex"); 18 | this.serialPort.write("buffer", function(err, results) { 19 | if (err) console.log('err ' + err); 20 | console.log('results ' + results); 21 | }.bind(this)); 22 | }.bind(this)); 23 | }; 24 | 25 | Serial.prototype.exec = function(command, data) { 26 | if (!(command in this.commands)) return; 27 | console.log("* Serial Executing: " + command); 28 | var path = this.commands[command]; 29 | this.send(path); 30 | }; 31 | 32 | Serial.prototype.send = function(string) { 33 | this.serialPort.write(string); 34 | } 35 | 36 | 37 | 38 | util.inherits(Serial, EventEmitter); 39 | module.exports = Serial; -------------------------------------------------------------------------------- /modules/snmp_dev/snmp.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var snmp = require('net-snmp'); 4 | 5 | function SNMP(data) { 6 | this.host = data.host; 7 | this.traps = data.traps; 8 | this.session = snmp.createSession ("127.0.0.1", "public"); 9 | 10 | 11 | var oids = ["1.3.6.1.2.1.1.5.0", "1.3.6.1.2.1.1.6.0"]; 12 | 13 | this.session.get (oids, function (error, varbinds) { 14 | if (error) { 15 | console.error (error); 16 | } else { 17 | for (var i = 0; i < varbinds.length; i++) { 18 | if (snmp.isVarbindError(varbinds[i])) { 19 | console.error (snmp.varbindError (varbinds[i])); 20 | } else { 21 | console.log (varbinds[i].oid + " = " + varbinds[i].value); 22 | } 23 | } 24 | } 25 | }); 26 | 27 | 28 | this.session.trap (snmp.TrapType.LinkDown, function (error) { 29 | if (error) 30 | console.error ("! SNMP ", error); 31 | }); 32 | }; 33 | util.inherits(SNMP, EventEmitter); 34 | 35 | SNMP.prototype.exec = function(command, data) { 36 | this.log(command); 37 | }; 38 | 39 | SNMP.prototype.log = function(data) { 40 | console.log("SNMP LOG:" + data); 41 | this.emit("DeviceEvent", "Logged"); 42 | } 43 | 44 | module.exports = SNMP; -------------------------------------------------------------------------------- /modules/gopro/gopro.js: -------------------------------------------------------------------------------- 1 | 2 | var EventEmitter = require('events').EventEmitter; 3 | var net = require('net'); 4 | var util = require('util'); 5 | var http = require('http'); 6 | var url = require('url'); 7 | 8 | function GoPro(data) { 9 | this.host = data.host || "10.9.9.5"; 10 | this.port = data.port || "8080"; 11 | this.password = data.password; 12 | this.debug = data.debug; 13 | } 14 | util.inherits(GoPro, EventEmitter); 15 | 16 | // 17 | GoPro.prototype.exec = function(command, params) { 18 | if (command == "StartCapture") { 19 | this.sendCommand("/bacpac/SH", 1); 20 | } else if (command == "StopCapture") { 21 | this.sendCommand("/bacpac/SH", 2); 22 | } 23 | }; 24 | 25 | GoPro.prototype.setMode = function(mode) { 26 | this.sendCommand("/bacpac/CM", mode); 27 | } 28 | 29 | //Reference: 30 | //http://www.techanswerguy.com/2013/02/capturing-live-stream-from-gopro-hero-2.html 31 | 32 | GoPro.prototype.sendCommand = function(path, value) { 33 | var query = {password: this.password, p:"%0" + value} 34 | var commandURL = url.format({protocol:"http", host:this.host, pathname:path, query:query}) 35 | http.get(commandURL, function(res) { 36 | if (this.debug) console.log("Sent: ", commandURL); 37 | }).on('error', function(e) { 38 | if (this.debug) console.log("Error", commandURL); 39 | }); 40 | } 41 | module.exports = GoPro; 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Route 2 | Home automation and event router 3 | http://route.io/ 4 | 5 | ### Examples 6 | 7 | Getting started with Route is easy if you have JavaScript knowledge and 8 | the appropriate hardware. For example, here's a script that allows a single 9 | light switch button press to turn on multiple lights and your Sonos. 10 | 11 | var insteon = route.addDevice({ 12 | type : Insteon, 13 | name : "Insteon", 14 | init : { 15 | host : "10.0.1.120", 16 | devices : { 17 | "Switch" : "1F5450", 18 | "KitchenLights" : "1F32AA", 19 | "BedRoomLights" : "1FC81E", 20 | } 21 | } 22 | }); 23 | 24 | var sonos = route.addDevice({ 25 | type : Sonos, 26 | name : "Sonos", 27 | init : { 28 | host : "10.0.1.16", 29 | } 30 | }); 31 | 32 | route.addEventMap({ 33 | "Switch.Remote.On" : [ 34 | "Insteon.BedRoomLights.On", 35 | "Insteon.KitchenLights.On", 36 | "Sonos.Play" 37 | ], 38 | "Switch.Remote.Off" : [ 39 | "Insteon.BedRoomLights.Off", 40 | "Insteon.KitchenLights.Off", 41 | "Sonos.Pause" 42 | ] 43 | }); 44 | 45 | You can see more usages in [the examples](http://github.com/glenmurphy/route/examples/) 46 | 47 | ### Installation 48 | 49 | npm install route.io 50 | npm install route.io-sonos 51 | npm install route.io-web -------------------------------------------------------------------------------- /modules/facebook/facebook.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var fb = require('fb'); 3 | var util = require('util'); 4 | 5 | function Facebook(data) { 6 | this.app_id = data.app_id; 7 | this.app_secret = data.app_secret; 8 | this.accessToken; 9 | } 10 | util.inherits(Facebook, EventEmitter); 11 | 12 | Facebook.prototype.auth = function() { 13 | fb.api('oauth/access_token', { 14 | client_id: this.app_id, 15 | client_secret: this.app_secret, 16 | grant_type: 'client_credentials' 17 | }, function (res) { 18 | if(!res || res.error) { 19 | console.log(!res ? 'error occurred' : res.error); 20 | return; 21 | } 22 | 23 | this.accessToken = res.access_token; 24 | console.log(this.accessToken); 25 | fb.setAccessToken(this.accessToken); 26 | this.get(); 27 | }.bind(this)); 28 | }; 29 | 30 | Facebook.prototype.get = function() { 31 | fb.api('music.listens', function (res) { 32 | if(!res || res.error) { 33 | console.log(!res ? 'error occurred' : res.error); 34 | return; 35 | } 36 | console.log(res); 37 | }); 38 | }; 39 | 40 | Facebook.prototype.post = function() { 41 | 42 | fb.api('me/feed', 'post', {message: "TEST"}, function (res) { 43 | if(!res || res.error) { 44 | console.log(!res ? 'error occurred' : res.error); 45 | return; 46 | } 47 | console.log('Post Id: ' + res.id); 48 | }); 49 | }; 50 | 51 | module.exports = Facebook; 52 | -------------------------------------------------------------------------------- /examples/tools/speech.PIE: -------------------------------------------------------------------------------- 1 | if said("house, turn the bedroom lights on please", 5) then 2 | BeepAsterisk() 3 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "BedRoomLightsOn") 4 | endif 5 | 6 | if said("house, turn the bedroom lights off please", 5) then 7 | BeepAsterisk() 8 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "BedRoomLightsOff") 9 | endif 10 | 11 | if said("house, turn the bedroom lamp on please", 5) then 12 | BeepAsterisk() 13 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "BedRoomLampOn") 14 | endif 15 | 16 | if said("house, turn the bedroom lamp off please", 5) then 17 | BeepAsterisk() 18 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "BedRoomLampOff") 19 | endif 20 | 21 | if said("house, turn the livingroom lights on please", 5) then 22 | BeepAsterisk() 23 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "LivingRoomLightsOn") 24 | endif 25 | 26 | if said("house, turn the livingroom lights off please", 5) then 27 | BeepAsterisk() 28 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "LivingRoomLightsOff") 29 | endif 30 | 31 | if said("house, start the music please", 6) then 32 | BeepAsterisk() 33 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "Play") 34 | endif 35 | 36 | if said("house, stop the music please", 6) then 37 | BeepAsterisk() 38 | Execute("C:\Dropbox\Projects\emerson\remotes\lights.pyw", "Pause") 39 | endif 40 | -------------------------------------------------------------------------------- /modules/state/state.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | 4 | function State(debug) { 5 | if (debug) this.debug=true; 6 | this.values = {}; 7 | }; 8 | util.inherits(State, EventEmitter); 9 | 10 | State.prototype.allValues = function () { 11 | return this.values; 12 | } 13 | 14 | function setValueForKeyPath(object, value, keypath) { 15 | // All keys are treated as keypaths, allowing sub-element 16 | // modification for highly noisy sources 17 | var components = keypath.split("."); 18 | var parent = object; 19 | var component; 20 | while (component = components.shift()) { 21 | if (components.length) { 22 | if (!parent[component]) parent[component] = {}; 23 | parent = parent[component]; 24 | } else if (value != undefined) { 25 | parent[component] = value; 26 | } else { 27 | delete parent[component]; 28 | } 29 | } 30 | } 31 | 32 | State.prototype.setValueForKeyPath = function(value, keypath) { 33 | setValueForKeyPath(this.values, value, keypath); 34 | } 35 | 36 | State.prototype.addValues = function(values) { 37 | for (var keypath in values) { 38 | var value = values[keypath]; 39 | this.setValueForKeyPath(value, keypath); 40 | //this.values[keypath] = value; 41 | } 42 | if (this.debug) { 43 | this.emit("StateEvent", this.allValues()); 44 | } else { 45 | this.emit("StateEvent", values); 46 | } 47 | 48 | } 49 | 50 | module.exports = State; -------------------------------------------------------------------------------- /modules/asana/asana.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var fs = require('fs'); 4 | 5 | var asana = require('asana-api'); 6 | 7 | function Asana(data) { 8 | this.key = data.key; 9 | this.workspaces = {}; 10 | this.projects = {}; 11 | this.client = asana.createClient({apiKey: this.key}); 12 | 13 | this.client.workspaces.list(function (err, workspaces) { 14 | if (!workspaces) return; 15 | for (var i = 0; i < workspaces.length; i++) { 16 | this.workspaces[workspaces[i].name] = workspaces[i].id; 17 | }; 18 | }.bind(this)); 19 | 20 | this.client.projects.list(function (err, projects) { 21 | if (!projects) return; 22 | for (var i = 0; i < projects.length; i++) { 23 | this.projects[projects[i].name] = projects[i].id; 24 | }; 25 | }.bind(this)); 26 | } 27 | util.inherits(Asana, EventEmitter); 28 | 29 | Asana.prototype.exec = function(command, params) { 30 | if (command == "AddTask") { 31 | this.addTask(params); 32 | } else { 33 | this.log(command); 34 | } 35 | }; 36 | 37 | Asana.prototype.addTask = function(params) { 38 | 39 | var workspaceId = this.workspaces[params.workspace] || params.workspace; 40 | var projectId = this.projects[params.project] || params.project; 41 | console.log(params, workspaceId, projectId); 42 | this.client.tasks.create(workspaceId, projectId, params, function (err, projects) { 43 | console.log("Added task: ", projects); 44 | }); 45 | } 46 | 47 | module.exports = Asana; -------------------------------------------------------------------------------- /modules/insteon_ip_camera/insteon_ip_camera.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var fs = require('fs'); 3 | var http = require('http'); 4 | var net = require('net'); 5 | var url = require('url'); 6 | var util = require('util'); 7 | 8 | function InsteonIPCamera(data) { 9 | this.host = data.host; 10 | this.port = data.port || "80"; 11 | this.username = data.username; 12 | this.password = data.password; 13 | this.dir = data.dir || "../.."; 14 | this.debug = data.debug; 15 | } 16 | util.inherits(InsteonIPCamera, EventEmitter); 17 | 18 | InsteonIPCamera.prototype.exec = function(command, params) { 19 | if (command == "Capture") { 20 | this.sendCommand("/snapshot.cgi"); 21 | } 22 | }; 23 | 24 | InsteonIPCamera.prototype.sendCommand = function(path, value) { 25 | var options = { 26 | host: this.host, 27 | port: 80, 28 | path: path, 29 | headers: { 30 | 'Authorization': 'Basic ' + new Buffer(this.username + ':' + this.password).toString('base64') 31 | } 32 | }; 33 | 34 | http.get(options, function(res) { 35 | var imagedata = '' 36 | res.setEncoding('binary') 37 | 38 | res.on('data', function(chunk){ 39 | imagedata += chunk 40 | }); 41 | 42 | res.on('end', function(){ 43 | fs.writeFile(this.dir + 'Front door ' + new Date() +'.png', imagedata, 'binary', function(err){ 44 | if (err) throw err 45 | console.log('File saved.') 46 | }); 47 | }.bind(this)); 48 | 49 | }.bind(this)).on('error', function(e) { 50 | console.log("Got error: " + e.message); 51 | }); 52 | } 53 | 54 | exports.InsteonIPCamera = InsteonIPCamera; 55 | -------------------------------------------------------------------------------- /modules/alexa/lighting_api_lambda.js: -------------------------------------------------------------------------------- 1 | //This is a function for use on AWS Lambda to bridge lighting. 2 | 3 | // Create a home skill 4 | // Create a lambda function (content below) 5 | // Information: https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/steps-to-create-a-smart-home-skill 6 | 7 | // Setup login with amazon: https://developer.amazon.com/lwa/sp/overview.html 8 | // You may have to create one with a valid return URL: 9 | // something like https://pitangui.amazon.com/api/skill/link/M2NVWMSP5HFVG1 10 | 11 | // Add the test skill at: http://alexa.amazon.com/spa/index.html#skills/smartHome 12 | 13 | // OAuth Scope 14 | // profile 15 | 16 | // OAuth authorization URL 17 | // https://www.amazon.com/ap/oa 18 | 19 | // OAuth token URL 20 | // https://api.amazon.com/auth/o2/token 21 | 22 | // Amazon Customer ID 23 | // (get from https://amazon.com/profile (id will be appened)) 24 | 25 | exports.handler = function(event, context) { 26 | var eventJson = JSON.stringify(event); 27 | var options = { 28 | host: '[IP_ADDR]', port: 443, path: '/alexa-lighting', method: 'POST', 29 | headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(eventJson)}, 30 | rejectUnauthorized: false 31 | }; 32 | 33 | var req = require('https').request(options, function(res) { 34 | res.on('data', function (data) {res.data = (res.data || "") + data}); 35 | res.on('end', function() { context.succeed(JSON.parse(res.data));}); 36 | }); 37 | req.on('error', function(e) { context.fail(e);}); 38 | req.write(eventJson); 39 | req.end(); 40 | }; 41 | -------------------------------------------------------------------------------- /modules/time/time.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | 4 | function Time(data) { 5 | this.host = data.host; 6 | this.route = data.route; 7 | this.emitEvents(); 8 | }; 9 | util.inherits(Time, EventEmitter); 10 | 11 | Time.PRESETS = { 12 | "1200" : "Noon", 13 | "0000" : "Midnight", 14 | } 15 | 16 | Time.prototype.emitEvents = function() { 17 | var time = new Date(); 18 | //time.setSeconds(0).setMilliseconds(0); 19 | var hours = time.getHours(); 20 | var minutes = time.getMinutes(); 21 | if (hours < 10) hours = "0" + hours.toString(); 22 | if (minutes < 10) minutes = "0" + minutes.toString(); 23 | var string = util.format('%s%s', hours, minutes); 24 | 25 | // If we have access to route, verify execution to avoid noise 26 | if (this.route) var eventCount = this.route.allEventsMatchingName("Time." + string).length; 27 | if (!this.route || eventCount) { 28 | this.emit("DeviceEvent", string); 29 | } 30 | 31 | // Emit presets without checking, since they are non-noisy 32 | if (Time.PRESETS[string]) { 33 | this.emit("DeviceEvent", Time.PRESETS[string]); 34 | } 35 | 36 | var nextTime = time; 37 | time.setSeconds(0); 38 | time.setMilliseconds(0); // zero out cruft 39 | var nextTime = new Date(time.getTime() + 1000 * 60); // next minute 40 | var delay = nextTime - new Date(); 41 | setTimeout(this.emitEvents.bind(this), delay); 42 | } 43 | 44 | Time.prototype.log = function(data) { 45 | console.log("Time LOG:" + data); 46 | this.emit("DeviceEvent", "Logged"); 47 | } 48 | 49 | module.exports = Time; -------------------------------------------------------------------------------- /modules/ical/ical.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var exec = require('child_process').exec; 4 | 5 | function iCal(data) { 6 | this.listenPort = data.port || 9011; 7 | this.calendars = data.calendars; 8 | this.updateCalendar(); 9 | setInterval(this.updateCalendar.bind(this), 60 * 60 * 1000); 10 | }; 11 | util.inherits(iCal, EventEmitter); 12 | 13 | iCal.prototype.updateCalendar = function(probe) { 14 | 15 | var command = "icalBuddy -std -ea -n -ps '|\t|' -b '' -npn -ic '" + this.calendars.join(",") + "' -nrd -iep 'datetime,title,location' -po 'datetime, title' -df '%B %d, %Y' -tf '%H:%M:%S' eventsToday+1" 16 | try { 17 | exec(command, function(error, stdout, stderr) { 18 | var lines = stdout.split("\n"); 19 | var events = []; 20 | for (var i = 0; i < lines.length; i++) { 21 | var line = lines[i]; 22 | try { 23 | var info = {} 24 | var fields = line.split("\t"); 25 | 26 | var when = fields[0].match(/(.*) at (.*) - (.*)/); 27 | info.start = new Date(when[1] + " " + when[2]); 28 | info.end = new Date(when[1] + " " + when[3]); 29 | 30 | var what = fields[1].match(/(.*) \((.*)\)/); 31 | info.title = what[1]; 32 | info.calendar = what[2]; 33 | if (fields[2]) info.location = fields[2]; 34 | events.push(info); 35 | } catch (e) { 36 | //console.log("! Ignoring", line) 37 | } 38 | 39 | this.emit("StateEvent", {calendar: events}); 40 | }; 41 | }.bind(this)); 42 | } catch (e) { 43 | console.log("! iCal Error:", e); 44 | } 45 | 46 | } 47 | 48 | module.exports = iCal; 49 | -------------------------------------------------------------------------------- /examples/tools/insteon_direct.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | var readline = require('readline'); 3 | 4 | function InsteonDirect(host) { 5 | this.host = host; 6 | this.rl = readline.createInterface({ 7 | input: process.stdin, 8 | output: process.stdout, 9 | terminal : true 10 | }); 11 | this.rl.setPrompt("> "); 12 | this.rl.on("line", this.handleInput.bind(this)); 13 | this.connect(); 14 | } 15 | 16 | InsteonDirect.PORT = 9761; 17 | 18 | InsteonDirect.prototype.connect = function() { 19 | this.reconnecting_ = false; 20 | this.client = net.connect({ 21 | host : this.host, 22 | port : InsteonDirect.PORT 23 | }, this.handleConnected.bind(this)); 24 | this.client.on('data', this.handleData.bind(this)); 25 | this.client.on('error', this.handleError.bind(this)); 26 | }; 27 | 28 | InsteonDirect.prototype.reconnect = function() { 29 | if (this.reconnecting_) return; 30 | 31 | this.reconnecting_ = true; 32 | setTimeout(this.connect.bind(this), 1000); 33 | } 34 | 35 | InsteonDirect.prototype.handleConnected = function() { 36 | console.log("Insteon Connected\n"); 37 | }; 38 | 39 | InsteonDirect.prototype.handleInput = function(data) { 40 | try { 41 | data = new Buffer(data, "hex"); 42 | this.client.write(data); 43 | } catch(e) { 44 | console.log("Bad input"); 45 | } 46 | }; 47 | 48 | InsteonDirect.prototype.handleData = function(data) { 49 | try { 50 | data = new Buffer(data).toString("hex").toUpperCase(); 51 | } catch(e) { 52 | console.log("*** Bad Data"); 53 | } 54 | console.log("RECV." + data); 55 | }; 56 | 57 | InsteonDirect.prototype.handleError = function(err) { 58 | this.rl.write("*** ERROR ***\n"); 59 | }; 60 | 61 | new InsteonDirect("10.0.1.120"); -------------------------------------------------------------------------------- /modules/caltrain/caltrain.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | 4 | var caltrain = require("nextcaltrain"); 5 | 6 | function Caltrain(data) { 7 | this.debug = data.debug; 8 | this.nightStation = data.nightStation; 9 | this.dayStation = data.dayStation; 10 | setTimeout(this.updateCaltrain.bind(this),2000) 11 | }; 12 | util.inherits(Caltrain, EventEmitter); 13 | 14 | Caltrain.prototype.updateCaltrain = function() { 15 | var schedule = caltrain({ 16 | from: this.nightStation, 17 | to: this.dayStation, 18 | date: new Date() 19 | })(); 20 | var state = {"caltrain": schedule} 21 | // console.log(state) 22 | this.emit("StateEvent", state); 23 | // console.log("this.nightStation", this.nightStation) 24 | // caltrain(function(err, getSchedule) { 25 | // if (err) { 26 | // throw err; 27 | // } 28 | 29 | // var getTrip; 30 | // try { 31 | // getTrip = getSchedule({ 32 | // from: this.nightStation, 33 | // to: this.dayStation, 34 | // date: new Date(), 35 | // }); 36 | // } 37 | // catch (err) { 38 | // if (err.code === "STOP_NOT_FOUND") { 39 | // console.error(err.message); 40 | // return; 41 | // } 42 | // } 43 | 44 | // console.log(format.heading(getTrip.from, getTrip.to)); 45 | 46 | // for (var i = 0; i < args.number; i++) { 47 | // if (i !== 0) { 48 | // console.log(); 49 | // } 50 | // console.log(format.trip(getTrip())); 51 | // } 52 | // }); 53 | // var nextCheck = (expires - new Date())/1000; 54 | 55 | 56 | // var state = {} 57 | // state[this.name] = data 58 | // console.log("*", state) 59 | 60 | // setTimeout(this.fetchRainCaltrain.bind(this), nextCheck * 1000); 61 | } 62 | 63 | Caltrain.prototype.exec = function(command, data) { 64 | }; 65 | 66 | module.exports = Caltrain; -------------------------------------------------------------------------------- /modules/pebble/pebble.js: -------------------------------------------------------------------------------- 1 | String.prototype.camelcase = function() { 2 | return this.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); }).replace(/ /g, ""); 3 | }; 4 | 5 | // Very simple Pebble processor 6 | // Converts input of "bake a pie" to "Pebble.BakeAPie" 7 | // "the" is discarded 8 | // synonyms are remapped 9 | 10 | var EventEmitter = require('events').EventEmitter; 11 | var util = require('util'); 12 | var url = require('url'); 13 | var http = require('http'); 14 | 15 | function Pebble(data) { 16 | this.port = data.port || 9002 17 | this.ids = data.ids; 18 | this.menuCallback = data.menuCallback; 19 | this.server = http.createServer(this.httpReq.bind(this)).listen(this.port); 20 | }; 21 | util.inherits(Pebble, EventEmitter); 22 | 23 | 24 | Pebble.prototype.httpReq = function(req, res) { 25 | var id = req.headers['x-pebble-id']; 26 | var device = this.ids[id]; 27 | console.log(device, id); 28 | if (device) { 29 | var info = url.parse(req.url, true); 30 | var action = info.path.substring(1); 31 | console.log("action", action); 32 | if (req.method == "POST") { 33 | req.setEncoding('utf8'); 34 | req.data = ""; 35 | req.on('data', function(chunk) { req.data += chunk; }); 36 | req.on('end', function() { 37 | console.log("data", req.data); 38 | var data = {}; 39 | try { 40 | data = JSON.parse(req.data); 41 | } catch(e) {} 42 | this.emit("DeviceEvent", device + "." + action, data); 43 | var resData = {1 : "Success"}; 44 | res.writeHead(200); 45 | res.write(JSON.stringify(resData)); 46 | res.end(); 47 | }.bind(this)); 48 | } 49 | } else { 50 | res.writeHead(200); 51 | res.write(JSON.stringify(this.menuCallback())); 52 | res.end(); 53 | } 54 | }; 55 | 56 | Pebble.prototype.exec = function(command, params) { 57 | }; 58 | 59 | module.exports = Pebble; -------------------------------------------------------------------------------- /examples/emerson/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Emerson 5 | 33 | 41 | 42 | MAIN
43 |   LIGHTS ON 44 | OFF 45 |
46 |   SONOS ON 47 | OFF 48 |
49 | STUDY
50 |   LIGHTS ON 51 | OFF 52 |
53 |   LAMP ON 54 | OFF 55 |
56 | BED
57 |   LIGHTS ON 58 | OFF 59 | 60 | -------------------------------------------------------------------------------- /modules/forecast/forecast.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var https = require('https'); 4 | var SunCalc = require("suncalc"); 5 | 6 | function Forecast(data) { 7 | this.debug = data.debug; 8 | this.latitude = data.latitude; 9 | this.longitude = data.longitude; 10 | this.forecastKey = data.forecastKey 11 | this.currentConditions = undefined; 12 | this.name = data.name || "Forecast" 13 | this.fetchRainForecast(); 14 | }; 15 | util.inherits(Forecast, EventEmitter); 16 | 17 | Forecast.prototype.fetchRainForecast = function() { 18 | var path = '/forecast/'+this.forecastKey+'/'+this.latitude+','+this.longitude; 19 | var req = https.get({ 20 | host: 'api.forecast.io', 21 | port: 443, 22 | path : path 23 | }, function(res) { 24 | res.setEncoding("utf8"); 25 | res.data = ''; 26 | res.on('data', function(d) { res.data += d; }); 27 | res.on('end', function() { this.parseRainForecast(res.data, res.headers);}.bind(this)); 28 | }.bind(this)); 29 | req.on('error', function(e) { 30 | console.log('forecast: ' + e.message); 31 | }); 32 | req.end(); 33 | } 34 | 35 | Forecast.prototype.parseRainForecast = function(data, headers) { 36 | var expires = new Date(headers.expires); 37 | var nextCheck = (expires - new Date())/1000; 38 | if (!data) return null; 39 | 40 | try { 41 | data = JSON.parse(data); 42 | var newConditions = data.currently.summary; 43 | if (this.currentConditions != newConditions) { 44 | this.currentConditions = newConditions; 45 | this.emit("DeviceEvent", newConditions.replace(" ", "")); 46 | } 47 | var state = {} 48 | state[this.name] = data 49 | this.emit("StateEvent", state); 50 | nextCheck = Math.max(10, Math.min(nextCheck, 1800)); 51 | if (this.debug) console.log("Forecast checking in", nextCheck); 52 | setTimeout(this.fetchRainForecast.bind(this), nextCheck * 1000); 53 | } catch(e) { 54 | console.log("Forecast: Error parsing:" + e); 55 | } 56 | } 57 | 58 | Forecast.prototype.exec = function(command, data) { 59 | console.log("Forecast:" + command); 60 | }; 61 | 62 | module.exports = Forecast; -------------------------------------------------------------------------------- /modules/bt-proximity/bt-proximity.js: -------------------------------------------------------------------------------- 1 | var noble = require('noble'), 2 | util = require('util'), 3 | EventEmitter = require('events').EventEmitter; 4 | 5 | function BTProximity(data) { 6 | this.mac = data.mac; 7 | this.name = data.name; 8 | 9 | noble.on('stateChange', this.handleStateChange.bind(this)); 10 | noble.on('discover', this.handleDiscover.bind(this)); 11 | 12 | setInterval(this.checkAway.bind(this), 5000); 13 | } 14 | util.inherits(BTProximity, EventEmitter); 15 | 16 | BTProximity.AWAYAFTERTIME = 90000; // how long we can go without seeing a device before we consider you away 17 | 18 | BTProximity.prototype.handleStateChange = function(state) { 19 | console.log(state); 20 | if (state === "poweredOn") { 21 | console.log("BTProximity: Powered on"); 22 | this.init(); 23 | } 24 | }; 25 | 26 | BTProximity.prototype.exec = function(command, data) { 27 | }; 28 | 29 | BTProximity.prototype.init = function() { 30 | console.log("Initing..."); 31 | noble.startScanning([], true); 32 | if (!this.lastSeen) { 33 | this.lastSeen = new Date().getTime(); // Pretend we're here. 34 | this.present = true; 35 | } 36 | }; 37 | 38 | BTProximity.prototype.shutdown = function() { 39 | }; 40 | 41 | BTProximity.prototype.checkAway = function() { 42 | if (this.lastSeen + BTProximity.AWAYAFTERTIME < new Date().getTime()) { 43 | this.setAway(); 44 | } 45 | }; 46 | 47 | BTProximity.prototype.setAway = function() { 48 | if (!this.present) return; 49 | 50 | this.present = false; 51 | console.log("BTProximity: User is away " + (new Date()).toLocaleTimeString()); 52 | this.emit("DeviceEvent", "Away"); 53 | }; 54 | 55 | BTProximity.prototype.setPresent = function() { 56 | this.lastSeen = new Date().getTime(); 57 | if (this.present) return; 58 | 59 | this.present = true; 60 | console.log("BTProximity: User found " + (new Date()).toLocaleTimeString()); 61 | this.emit("DeviceEvent", "Present"); 62 | }; 63 | 64 | BTProximity.prototype.handleDiscover = function(peripheral) { 65 | // Convert UUID to MAC 66 | var mac = peripheral.uuid.toUpperCase().replace(/(.{2})(?=.)/g,"$1:"); 67 | if (mac == this.mac) { 68 | this.setPresent(); 69 | } 70 | }; 71 | 72 | exports.BTProximity = BTProximity; 73 | -------------------------------------------------------------------------------- /modules/apn/apn.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var apns = require('apn'); 4 | 5 | function APN(data) { 6 | this.host = data.host; 7 | 8 | this.options = { 9 | cert: data.cert, /* Certificate file path */ 10 | certData: null, /* String or Buffer containing certificate data, if supplied uses this instead of cert file path */ 11 | key: data.key, /* Key file path */ 12 | keyData: null, /* String or Buffer containing key data, as certData */ 13 | passphrase: this.passphrase, /* A passphrase for the Key file */ 14 | ca: null, /* String or Buffer of CA data to use for the TLS connection */ 15 | gateway: data.gateway || 'gateway.push.apple.com', /* gateway address */ 16 | port: 2195, /* gateway port */ 17 | enhanced: true, /* enable enhanced format */ 18 | errorCallback: this.connectionError, /* Callback when error occurs function(err,notification) */ 19 | cacheLength: 100 /* Number of notifications to cache for error purposes */ 20 | }; 21 | 22 | this.apnsConnection = new apns.Connection(this.options); 23 | this.users = data.users; 24 | //Next, create a notification object and set parameters. See the payload documentation for more details. 25 | 26 | }; 27 | 28 | util.inherits(APN, EventEmitter); 29 | APN.prototype.connectionError = function(err, notification) { 30 | console.log("! ERROR sending APN", err, notification); 31 | } 32 | 33 | APN.prototype.exec = function(command, params) { 34 | if (command == "Push") { 35 | var token = this.users[params.user]; 36 | var note = new apns.Notification(); 37 | note.device = new apns.Device(token); 38 | note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now. 39 | if (params.sound) note.sound = params.sound; 40 | if (params.msg) note.alert = params.msg; 41 | if (params.url) note.payload = {'url': params.url}; 42 | this.apnsConnection.sendNotification(note); 43 | } else { 44 | this.log(command); 45 | } 46 | }; 47 | 48 | APN.prototype.log = function(data) { 49 | console.log("DUMMY LOG:" + data); 50 | this.emit("DeviceEvent", "Logged"); 51 | } 52 | 53 | module.exports = APN; -------------------------------------------------------------------------------- /modules/timer/timer.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | 4 | function Timer(data) { 5 | this.host = data.host; 6 | this.state = data.state; 7 | this.timers = []; 8 | this.timeouts = []; 9 | }; 10 | util.inherits(Timer, EventEmitter); 11 | 12 | Timer.prototype.exec = function(command, params) { 13 | if (command == "SetAlarm") { 14 | this.setAlarm(params.alarmTime, params.event, params.context); 15 | } else { 16 | this.log(command); 17 | } 18 | }; 19 | 20 | Timer.prototype.setAlarm = function(alarmTime, event, context) { 21 | var date = this.dateFromSpecifier(alarmTime); 22 | var record = {context:context, date:date, event:event, specifier:alarmTime}; 23 | this.timers.push(record); 24 | this.schedule(record); 25 | } 26 | 27 | Timer.prototype.schedule = function(record) { 28 | var now = new Date(); 29 | var delay = record.date.getTime() - now.getTime(); 30 | console.log("Scheduling", record, delay); 31 | 32 | 33 | 34 | var timeout = setTimeout(this.timerDone.bind(this, record), delay); 35 | } 36 | 37 | Timer.prototype.timerDone = function(record) { 38 | var params = {}; 39 | if (record.context) params.context = record.context; 40 | if (record.specifier) params.specifier = record.specifier; 41 | if (record.event) this.emit("DeviceEvent", record.event, params); 42 | } 43 | 44 | Timer.prototype.setDateTimeout = function(fn, d) { 45 | var t = d.getTime() - (new Date()).getTime(); 46 | if (t > 0) return setTimeout(fn, t); 47 | } 48 | 49 | Timer.prototype.dateFromSpecifier = function(spec) { 50 | try { 51 | var sunDate = this.state.allValues().sunEvents[spec]; 52 | if (sunDate) return sunDate; 53 | } catch (e) {} 54 | var offset = 0; 55 | var seconds = spec.split("second"); 56 | if (seconds.length > 1) offset = seconds[0]; 57 | 58 | var minutes = spec.split("minute"); 59 | if (minutes.length > 1) offset = 60 * minutes[0]; 60 | 61 | var hours = spec.split("hour"); 62 | if (hours.length > 1) offset = 60 * 60 * hours[0]; 63 | 64 | this.emit("DeviceEvent", "Say", {string: "Set timer "}); 65 | 66 | var now = new Date(); 67 | var milli = now.getTime(); 68 | milli += offset * 1000; 69 | 70 | return new Date(milli); 71 | } 72 | 73 | Timer.prototype.log = function(data) { 74 | console.log("Timer LOG:" + data); 75 | this.emit("DeviceEvent", "Logged"); 76 | } 77 | 78 | module.exports = Timer; -------------------------------------------------------------------------------- /modules/talk/talk.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | const xmpp = require('node-xmpp'); 3 | const request_helper = require('request'); 4 | const util = require('util'); 5 | //const process = require('process'); 6 | 7 | function Talk(data) { 8 | this.host = data.host; 9 | this.status_message = data.status_message, 10 | this.jid = data.jid 11 | this.password = data.password || process.env.bot_password, 12 | this.host = data.host || "talk.google.com", 13 | this.port = data.port || 5222, 14 | this.reconnect = data.reconnect || true 15 | // this.allow_auto_subscribe = data.allow_auto_subscribe || false, 16 | this.command_argument_separator = /\s*\;\s*/ 17 | 18 | this.conn = new xmpp.Client(this); 19 | this.conn.socket.setTimeout(0); 20 | this.conn.socket.setKeepAlive(true, 10000); 21 | 22 | this.conn.on('online', function() { 23 | this.setStatusMessage(this.status_message); 24 | }.bind(this)); 25 | this.conn.on('error', function(stanza) { 26 | util.log('[error] ' + stanza.toString()); 27 | }); 28 | 29 | this.conn.addListener('stanza', this.handleMessage.bind(this)); 30 | process.on('exit', function () { 31 | console.log("Terminating talk"); 32 | this.conn.end(); 33 | }.bind(this)); 34 | }; 35 | util.inherits(Talk, EventEmitter); 36 | 37 | Talk.prototype.handleMessage = function (stanza) { 38 | if('error' === stanza.attrs.type) { 39 | util.log('[error] ' + stanza.toString()); 40 | } else if(stanza.is('message')) { 41 | var body = stanza.getChildText('body'); 42 | var from = stanza.attrs.from.split("/").shift(); 43 | 44 | this.sendMessage(from, "Got it. (" + body + ")"); 45 | this.emit("DeviceEvent", "TextInput", {string:body, from:from}); 46 | 47 | } 48 | } 49 | 50 | Talk.prototype.sendMessage = function(to, message) { 51 | var elem = new xmpp.Element('message', { to: to, type: 'chat' }) 52 | .c('body').t(message); 53 | this.conn.send(elem); 54 | } 55 | 56 | 57 | Talk.prototype.setStatusMessage = function(status_message) { 58 | var presence_elem = new xmpp.Element('presence', { }) 59 | .c('show').t('chat').up() 60 | .c('status').t(status_message); 61 | this.conn.send(presence_elem); 62 | } 63 | 64 | 65 | Talk.prototype.exec = function(command, data) { 66 | this.log(command); 67 | }; 68 | 69 | Talk.prototype.log = function(data) { 70 | console.log("Talk LOG:" + data); 71 | this.emit("DeviceEvent", "Logged"); 72 | } 73 | 74 | module.exports = Talk; -------------------------------------------------------------------------------- /modules/airvisual/airvisual.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var util = require('util'); 4 | 5 | var nextCheck = 1*60*60*1000; 6 | 7 | function AirVisual(data) { 8 | this.debug = data.debug; 9 | this.latitude = data.latitude; 10 | this.longitude = data.longitude; 11 | this.airVisualKey = data.airVisualKey; 12 | this.currentConditions = undefined; 13 | this.name = data.name || "AirQuality"; 14 | this.fetchAirQuality(); 15 | }; 16 | util.inherits(AirVisual, EventEmitter); 17 | 18 | AirVisual.prototype.fetchAirQuality = function() { 19 | var path = '/v2/nearest_city?lat'+this.latitude+'&lon='+this.longitude+'&key='+this.airVisualKey; 20 | var req = http.get({ 21 | host: 'api.airvisual.com', 22 | port: 80, 23 | path : path 24 | }, function(res) { 25 | res.setEncoding("utf8"); 26 | res.data = ''; 27 | res.on('data', function(d) { res.data += d; }); 28 | res.on('end', function() { this.parseAirQuality(res.data, res.headers);}.bind(this)); 29 | }.bind(this)); 30 | req.on('error', function(e) { 31 | console.log('airvisual: ' + e.message); 32 | }); 33 | req.end(); 34 | } 35 | 36 | AirVisual.prototype.parseAirQuality = function(data, headers) { 37 | if (!data) return null; 38 | 39 | try { 40 | data = JSON.parse(data); 41 | var aqi = data.data.current.pollution.aqius; 42 | if (this.aqi != aqi) { 43 | this.aqi = aqi; 44 | 45 | var category; 46 | 47 | if (aqi <= 50) { 48 | category = "Good"; 49 | } else if (aqi <= 100) { 50 | category = "Moderate"; 51 | } else if (aqi <= 150) { 52 | category = "UnhealthyForSensitiveGroups"; 53 | } else if (aqi <= 200) { 54 | category = "Unhealthy"; 55 | } else if (aqi <= 300) { 56 | category = "VeryUnhealthy"; 57 | } else if (aqi > 300) { 58 | category = "Hazardous"; 59 | } else { 60 | category = "Unknown"; 61 | } 62 | 63 | this.emit("DeviceEvent", category); 64 | } 65 | var state = {}; 66 | state[this.name] = data.data; 67 | this.emit("StateEvent", state); 68 | if (this.debug) console.log("AirVisual data", data); 69 | if (this.debug) console.log("AirVisual checking in", nextCheck); 70 | setTimeout(this.fetchAirQuality.bind(this), nextCheck); 71 | } catch(e) { 72 | console.log("AirVisual: Error parsing:" + e); 73 | } 74 | } 75 | 76 | AirVisual.prototype.exec = function(command, data) { 77 | console.log("AirVisual:" + command); 78 | }; 79 | 80 | module.exports = AirVisual; 81 | -------------------------------------------------------------------------------- /modules/mac_bridge/mac_bridge.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | var http = require('http'); 5 | var xml2js = require('xml2js'); 6 | 7 | String.prototype.endsWith = function(suffix) { 8 | return this.indexOf(suffix, this.length - suffix.length) !== -1; 9 | }; 10 | 11 | /* Bridge ------------------------------------------------------------------- */ 12 | function Bridge(data) { 13 | this.host = data.host; 14 | this.port = data.port; 15 | this.connect(); 16 | this.dataBuffer = null; 17 | } 18 | util.inherits(Bridge, EventEmitter); 19 | 20 | Bridge.prototype.exec = function(command, params) { 21 | console.log("* Bridge Executing: " + command); 22 | if (command == "Reconnect") { 23 | this.reconnect(); 24 | } else if (command == "Say") { 25 | this.sendEvent("Say:" + params.string); 26 | } else { 27 | this.sendEvent(command + (params.string ? ":" + params.string : "")); 28 | } 29 | }; 30 | 31 | Bridge.prototype.sendEvent = function(event) { 32 | this.client.write(event + "\n"); 33 | } 34 | 35 | Bridge.prototype.connect = function() { 36 | this.reconnecting_ = false; 37 | this.client = net.connect({ 38 | host : this.host, 39 | port : this.port 40 | }, this.handleConnected.bind(this)); 41 | this.client.setEncoding(); 42 | this.client.on('data', this.handleData.bind(this)); 43 | this.client.on('end', this.handleEnd.bind(this)); 44 | this.client.on('error', this.handleError.bind(this)); 45 | }; 46 | 47 | Bridge.prototype.reconnect = function() { 48 | if (this.reconnecting_) return; 49 | this.reconnecting_ = true; 50 | setTimeout(this.connect.bind(this), 10000); 51 | } 52 | 53 | Bridge.prototype.handleConnected = function() { 54 | this.emit("DeviceEvent", "Bridge.Connected"); 55 | this.emit("StateEvent", {BridgeConnected:true}); 56 | }; 57 | 58 | Bridge.prototype.handleData = function(data) { 59 | 60 | if (this.dataBuffer) { 61 | data = this.dataBuffer + data; 62 | this.dataBuffer = null; 63 | } 64 | 65 | if (data.endsWith("}\n\n")) { 66 | try { 67 | var json = JSON.parse(data); 68 | this.playerInfo = json; 69 | this.emit("StateEvent", json); 70 | } catch (e) { 71 | console.log("! Bridge", e); 72 | } 73 | this.dataBuffer = null; 74 | } else { 75 | this.dataBuffer = data; 76 | } 77 | }; 78 | 79 | Bridge.prototype.handleEnd = function() { 80 | //this.emit("DeviceEvent", "Bridge.Disconnected"); 81 | this.emit("StateEvent", {BridgeConnected:false}); 82 | this.reconnect(); 83 | }; 84 | 85 | Bridge.prototype.handleError = function(e) { 86 | console.log("! Mac Bridge\t" + e); 87 | this.reconnect(); 88 | }; 89 | 90 | module.exports = Bridge; 91 | -------------------------------------------------------------------------------- /examples/cambridge/web/status.js: -------------------------------------------------------------------------------- 1 | function Status(states, socket, parentNode) { 2 | /* 3 | { 4 | "Proximity.Present" : "Here" 5 | "Proximity.Away" : "Away" 6 | } 7 | */ 8 | this.states = states; 9 | this.socket = socket; 10 | this.lastUpdated = 0; 11 | 12 | for (var state in this.states) { 13 | this.socket.on(state, this.handleState.bind(this, state)); 14 | this.socket.emit("getState", state); 15 | } 16 | 17 | if (Status.CSS_APPENDED == false) { 18 | var css = document.createElement("style"); 19 | css.type = "text/css"; 20 | css.innerHTML = Status.CSS 21 | document.body.appendChild(css); 22 | Status.CSS_APPENDED = true; 23 | } 24 | 25 | setInterval(this.updateTime.bind(this), 5000); 26 | this.node = createElement("div", "status", parentNode); 27 | this.nodeName = createElement("div", "status-name", this.node, ""); 28 | this.nodeTime = createElement("div", "status-time", this.node, ""); 29 | } 30 | 31 | Status.CSS = " \ 32 | .status { \ 33 | position:relative; \ 34 | z-index:1; \ 35 | display:inline-block; \ 36 | width: 300px; \ 37 | height: 48px; \ 38 | border-radius: 3px; \ 39 | margin-bottom: 3px; \ 40 | background-clip: padding-box; \ 41 | box-sizing:border-box; \ 42 | font-family: Noto Sans, helvetica, arial, sans-serif; \ 43 | font-size:14px; \ 44 | transition:all 0.1s; \ 45 | } \ 46 | .status:last-child { \ 47 | margin-right:0px; \ 48 | } \ 49 | .status-name { \ 50 | position:absolute; \ 51 | top:10px; \ 52 | left:16px; \ 53 | font-weight:bold; \ 54 | width:100%; \ 55 | font-size:14px; \ 56 | color:#999; \ 57 | text-shadow:0px 1px 3px rgba(0, 0, 0, 0.75); \ 58 | } \ 59 | .status-time { \ 60 | position:absolute; \ 61 | top:26px; \ 62 | left:16px; \ 63 | width:100%; \ 64 | font-size:10px; \ 65 | color:#666; \ 66 | } \ 67 | .Status.on .status-name { \ 68 | } \ 69 | "; 70 | Status.CSS_APPENDED = false; 71 | 72 | Status.prototype.handleState = function(state, details) { 73 | console.log(details); 74 | if (details.stateUpdatedTime < this.lastUpdated) return; 75 | if (!(state in this.states)) return; 76 | 77 | this.nodeName.innerHTML = this.states[state]; 78 | this.lastUpdated = details.stateUpdatedTime; 79 | this.updateTime(); 80 | }; 81 | 82 | 83 | Status.prototype.updateTime = function() { 84 | var diff = -(this.lastUpdated - (new Date()).getTime()) / 1000; 85 | var num_unit = (diff < 60 && [Math.max(diff, 0), 'seconds']) || 86 | ((diff/=60) < 60 && [diff, 'minutes']) || 87 | ((diff/=60) < 72 && [diff, 'hours']) || 88 | [diff/=24, 'days']; 89 | 90 | // Round down 91 | num_unit[0] = Math.floor(num_unit[0]); 92 | // Singularize unit 93 | if (num_unit[0] == 1) { num_unit[1] = num_unit[1].replace(/s$/,""); } 94 | 95 | this.nodeTime.innerHTML = num_unit.join(" ") + " ago"; 96 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | node_modules/ 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # PDT-specific 30 | .buildpath 31 | 32 | 33 | ################# 34 | ## Visual Studio 35 | ################# 36 | 37 | ## Ignore Visual Studio temporary files, build results, and 38 | ## files generated by popular Visual Studio add-ons. 39 | 40 | # User-specific files 41 | *.suo 42 | *.user 43 | *.sln.docstates 44 | 45 | # Build results 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | *_i.c 49 | *_p.c 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.vspscc 64 | .builds 65 | *.dotCover 66 | 67 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 68 | #packages/ 69 | 70 | # Visual C++ cache files 71 | ipch/ 72 | *.aps 73 | *.ncb 74 | *.opensdf 75 | *.sdf 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | 81 | # ReSharper is a .NET coding add-in 82 | _ReSharper* 83 | 84 | # Installshield output folder 85 | [Ee]xpress 86 | 87 | # DocProject is a documentation generator add-in 88 | DocProject/buildhelp/ 89 | DocProject/Help/*.HxT 90 | DocProject/Help/*.HxC 91 | DocProject/Help/*.hhc 92 | DocProject/Help/*.hhk 93 | DocProject/Help/*.hhp 94 | DocProject/Help/Html2 95 | DocProject/Help/html 96 | 97 | # Click-Once directory 98 | publish 99 | 100 | # Others 101 | [Bb]in 102 | [Oo]bj 103 | sql 104 | TestResults 105 | *.Cache 106 | ClientBin 107 | stylecop.* 108 | ~$* 109 | *.dbmdl 110 | Generated_Code #added for RIA/Silverlight projects 111 | 112 | # Backup & report files from converting an old project file to a newer 113 | # Visual Studio version. Backup files are not needed, because we have git ;-) 114 | _UpgradeReport_Files/ 115 | Backup*/ 116 | UpgradeLog*.XML 117 | 118 | 119 | 120 | ############ 121 | ## Windows 122 | ############ 123 | 124 | # Windows image file caches 125 | Thumbs.db 126 | 127 | # Folder config file 128 | Desktop.ini 129 | 130 | 131 | ############# 132 | ## Python 133 | ############# 134 | 135 | *.py[co] 136 | 137 | # Packages 138 | *.egg 139 | *.egg-info 140 | dist 141 | build 142 | eggs 143 | parts 144 | bin 145 | var 146 | sdist 147 | develop-eggs 148 | .installed.cfg 149 | 150 | # Installer logs 151 | pip-log.txt 152 | 153 | # Unit test / coverage reports 154 | .coverage 155 | .tox 156 | 157 | #Translations 158 | *.mo 159 | 160 | #Mr Developer 161 | .mr.developer.cfg 162 | 163 | # Mac crap 164 | .DS_Store 165 | 166 | .jshintrc 167 | -------------------------------------------------------------------------------- /examples/cambridge/web/switch.js: -------------------------------------------------------------------------------- 1 | function Switch(name, desc, socket, parentNode) { 2 | this.name = name; 3 | this.desc = desc; 4 | this.socket = socket; 5 | 6 | this.socket.on(name, this.handleBrightness.bind(this)); 7 | this.socket.emit("getState", name); 8 | 9 | if (Switch.CSS_APPENDED == false) { 10 | var css = document.createElement("style"); 11 | css.type = "text/css"; 12 | css.innerHTML = Switch.CSS 13 | document.body.appendChild(css); 14 | Switch.CSS_APPENDED = true; 15 | } 16 | 17 | this.node = createElement("a", "switch", parentNode); 18 | this.nodeOn = createElement("div", "switch-on", this.node, ""); 19 | this.nodeName = createElement("div", "switch-name", this.node, this.desc); 20 | this.nodeOff = createElement("div", "switch-off", this.node, ""); 21 | 22 | this.node.addEventListener("click", this.handleClick.bind(this), false); 23 | } 24 | 25 | Switch.CSS = " \ 26 | .switch { \ 27 | position:relative; \ 28 | z-index:1; \ 29 | display:inline-block; \ 30 | width: 99px; \ 31 | margin-right:1px; \ 32 | margin-bottom:1px; \ 33 | height: 64px; \ 34 | background-clip: padding-box; \ 35 | background-color: #222; \ 36 | box-sizing:border-box; \ 37 | font-family: Noto Sans, helvetica, arial, sans-serif; \ 38 | font-size:14px; \ 39 | cursor:pointer; \ 40 | transition:all 0.1s; \ 41 | } \ 42 | .switch:first-child { \ 43 | margin-left:0px; \ 44 | } \ 45 | .switch:last-child { \ 46 | width:100px; \ 47 | margin-right:0px; \ 48 | } \ 49 | .switch:hover { \ 50 | background-color:#333; #235fa7; \ 51 | } \ 52 | .switch:active { \ 53 | margin-top:2px; \ 54 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.2), 0px 1px 5px 2px rgba(0, 0, 0, 0.2); \ 55 | } \ 56 | .switch.on { \ 57 | background-color:#494; \ 58 | border:1px solid #5b5; \ 59 | } \ 60 | .switch.on:hover { \ 61 | //background-color:#3a3a3a; \ 62 | } \ 63 | .switch-on { \ 64 | position:absolute; \ 65 | top:0px; \ 66 | left:0px; \ 67 | width:100px; \ 68 | } \ 69 | .switch-name { \ 70 | position:absolute; \ 71 | top:22px; \ 72 | left:0px; \ 73 | width:100%; \ 74 | font-size:14px; \ 75 | color:#888; \ 76 | text-align:center; \ 77 | } \ 78 | .switch.on .switch-name { \ 79 | color:#eee; \ 80 | text-shadow:0px 1px 0px rgba(0, 0, 0, 0.75); \ 81 | } \ 82 | "; 83 | Switch.CSS_APPENDED = false; 84 | 85 | Switch.prototype.handleBrightness = function(details) { 86 | console.log(details); 87 | var brightness = parseInt(details.brightness); 88 | if (isNaN(brightness)) return; 89 | this.brightness = brightness; 90 | if (brightness > 10) 91 | this.node.classList.add("on"); 92 | else 93 | this.node.classList.remove("on"); 94 | }; 95 | 96 | Switch.prototype.handleClick = function() { 97 | var state = (this.brightness > 10) ? "Off" : "On"; 98 | this.socket.emit("DeviceEvent", [this.name, state].join(".")); 99 | }; 100 | -------------------------------------------------------------------------------- /modules/jawbone/jawbone.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var https = require('https'); 4 | var url = require('url'); 5 | var util = require('util'); 6 | // var oauth = require('oauth'); 7 | var xml2js = require('xml2js'); 8 | var storage = require('../storage').Storage; 9 | 10 | Jawbone.PORT = 9041; 11 | function Jawbone(data) { 12 | this.listenPort = data.listenPort || Jawbone.PORT; 13 | this.server = http.createServer(this.handleReq.bind(this)).listen(this.listenPort); 14 | this.users = data.users; 15 | this.debug = data.debug; 16 | } 17 | util.inherits(Jawbone, EventEmitter); 18 | 19 | Jawbone.prototype.handleReq = function(req, res) { 20 | var info = url.parse(req.url, true); 21 | if (req.method == 'POST') { 22 | var data = new Buffer(''); 23 | req.on('data', function(chunk) { data = Buffer.concat([data, chunk]);}); 24 | req.on('end', function() { 25 | var string = data.toString('utf-8'); 26 | console.log(string); 27 | var updates = JSON.parse(string).events; 28 | res.writeHead(200, {}); 29 | res.end(); 30 | this.handleUpdates(updates); 31 | }.bind(this)); 32 | } else { 33 | res.writeHead(200, {}); 34 | res.end(); 35 | } 36 | }; 37 | 38 | Jawbone.prototype.handleUpdates = function(updates) { 39 | for (var i = 0; i < updates.length; i++) { 40 | var update = updates[i]; 41 | var user = this.userForId(update.user_xid); 42 | var action = update.action; 43 | console.log("* Jawbone updated", user, action, update); 44 | 45 | switch(action) { 46 | case "creation": 47 | case "updation": 48 | this.getEvent(update); 49 | break; 50 | case "deletion": 51 | break; 52 | default: 53 | console.log("Got action:", action); 54 | break; 55 | } 56 | }; 57 | } 58 | 59 | Jawbone.prototype.userForId = function(id) { 60 | for (var userid in this.users) { 61 | var info = this.users[userid]; 62 | if (info.xid = id) return userid; 63 | } 64 | } 65 | 66 | Jawbone.prototype.tokenForUser = function(userid) { 67 | return this.users[userid].token; 68 | } 69 | 70 | Jawbone.prototype.getEvent = function(update) { 71 | var user = this.userForId(update.user_xid); 72 | var urlBase = "https://jawbone.com/nudge/api/v.1.0/" + update.type + "/" + update.event_xid; 73 | var options = url.parse(urlBase); 74 | options.headers = { 'Authorization': "Bearer " + this.tokenForUser(user) } 75 | console.log(options, user); 76 | 77 | https.get(options, function(res) { 78 | console.log("Open Sesame", res.statusCode); 79 | if (res.statusCode == 200) { 80 | res.data = ""; 81 | res.on('data', function (d) {res.data += d;}); 82 | res.on("end", function() { 83 | console.log(res.data); 84 | }); 85 | } 86 | 87 | }).on('error', function(e) { 88 | console.log("Unable to open sesame"); 89 | }); 90 | } 91 | 92 | module.exports = Jawbone; 93 | -------------------------------------------------------------------------------- /modules/nest_dev/nest.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var nest = require('unofficial-nest-api') 4 | 5 | function Nest(data) { 6 | this.user = data.user; 7 | this.debug = data.debug; 8 | this.password = data.password; 9 | this.connect(); 10 | this.names = {}; 11 | this.ids = {}; 12 | }; 13 | util.inherits(Nest, EventEmitter); 14 | 15 | Nest.prototype.connect = function () { 16 | nest.login(this.user, this.password, function (err, data) { 17 | if (err) { 18 | console.log("Login failed", err); 19 | } else { 20 | //if (this.debug) console.log("Nest connected", data); 21 | nest.fetchStatus(this.statusUpdated.bind(this)); 22 | } 23 | }.bind(this)); 24 | } 25 | 26 | Nest.prototype.statusUpdated = function(data) { 27 | this.names = {}; 28 | for (var deviceId in data.device) { 29 | if (data.device.hasOwnProperty(deviceId)) { 30 | var device = data.shared[deviceId]; 31 | // console.log(deviceId, device.name, device); 32 | var shortName = device.name.replace(/ /g,"") || deviceId; 33 | this.ids[deviceId] = shortName; 34 | this.names[shortName] = deviceId; 35 | this.handleDeviceData(deviceId, device); 36 | } 37 | } 38 | // this.subscribe(); 39 | this.fetchData(); 40 | }; 41 | 42 | Nest.prototype.handleDeviceData = function(deviceId, data) { 43 | if (deviceId) { 44 | // console.log('SubscribedDevice: ' + deviceId); 45 | // if (this.debug) console.log(JSON.stringify(data)); 46 | var shortName = data.name.replace(/ /g,"") || deviceId; 47 | var temperature = nest.ctof(data.current_temperature); 48 | data.current_temperature_f = Math.round(temperature); 49 | var target_temperature = nest.ctof(data.target_temperature); 50 | data.target_temperature_f = Math.round(target_temperature); 51 | var state = {}; 52 | state["nest." + shortName] = data; 53 | this.emit("StateEvent", state); 54 | this.emit("DeviceEvent", shortName + "." + temperature); 55 | } 56 | } 57 | 58 | Nest.prototype.fetchData = function(data) { 59 | nest.subscribe(function (deviceId, data, type) { 60 | this.handleDeviceData(deviceId, data); 61 | setTimeout(this.fetchData.bind(this), 2000); 62 | }.bind(this), ['shared']); 63 | } 64 | 65 | Nest.prototype.setTemperature = function(thermostat, temperature) { 66 | var deviceId = this.names[thermostat]; 67 | console.log(thermostat, temperature, nest.ftoc(temperature)); 68 | nest.setTemperature(deviceId, nest.ftoc(temperature)); 69 | } 70 | 71 | Nest.prototype.setTargetTemperatureType = function(thermostat, type) { 72 | var deviceId = this.names[thermostat]; 73 | console.log(thermostat, type); 74 | nest.setTargetTemperatureType(deviceId, type); 75 | } 76 | 77 | Nest.prototype.exec = function(command, data) { 78 | this.log(command); 79 | }; 80 | 81 | Nest.prototype.log = function(data) { 82 | console.log("Nest LOG:" + data); 83 | this.emit("DeviceEvent", "Logged"); 84 | } 85 | 86 | module.exports = Nest; -------------------------------------------------------------------------------- /modules/spotify/spotify.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | var http = require('http'); 5 | var xml2js = require('xml2js'); 6 | 7 | /* SPOTIFY ------------------------------------------------------------------- */ 8 | function Spotify(data) { 9 | this.bridge = data.bridge; 10 | } 11 | util.inherits(Spotify, EventEmitter); 12 | 13 | 14 | Spotify.prototype.exec = function(command, params) { 15 | console.log("* Spotify Executing: " + command); 16 | console.log(params); 17 | if (command == "Reconnect") { 18 | this.reconnect(); 19 | } else if (command == "Listen") { 20 | this.listenTo(params.query, params.context); 21 | } else { 22 | this.bridge.exec("Spotify." + command); 23 | } 24 | }; 25 | 26 | // Find matching spotify urls matching a type/query 27 | Spotify.prototype.searchByType = function(type, query, callback) { 28 | console.log("http://ws.spotify.com/search/1/" + type + "?q=" + query); 29 | http.get("http://ws.spotify.com/search/1/" + type + "?q=" + query, function(res) { 30 | var body = ''; 31 | res.on('data', function (d) {body += d;}); 32 | res.on('end', function () { 33 | var parser = new xml2js.Parser(); 34 | parser.parseString(body, function (err, result) { 35 | if (result) { 36 | var array = result[type + "s"][type]; 37 | callback.bind(this)(array); 38 | } else { 39 | console.log("! no spotify results"); 40 | } 41 | }.bind(this)); 42 | }.bind(this)); 43 | }.bind(this)) 44 | } 45 | 46 | Spotify.prototype.playArtistByURI = function(uri) { 47 | 48 | 49 | } 50 | 51 | 52 | // Search artists and tracks to find the best match for a request 53 | Spotify.prototype.listenTo = function(query, context) { 54 | console.log("Searching for: " + query); 55 | if (!query) return; 56 | this.bridge.exec("Say", {string : "playing " + query.split(" by ").join(". by ") + "\n"}); 57 | query = query.split(" by ").join(" "); 58 | this.searchByType("artist", query, function (artists) { 59 | if (artists && artists.length && artists[0] && artists[0].popularity > 0.2) { 60 | return this.playArtist(artists[0]['$']['href']); 61 | return this.playInContext(artists[0]['$']['href'], null, context); 62 | } 63 | console.log("no artist matches"); 64 | this.searchByType("track", query, function (tracks) { 65 | if (tracks && tracks.length && tracks[0] && tracks[0].popularity > 0.2) { 66 | var track = tracks[0]['$']['href']; 67 | var album = tracks[0]['album'][0]['$']['href']; 68 | console.log(album); 69 | return this.playInContext(track, album, context); 70 | } 71 | console.log("no track matches"); 72 | }.bind(this)); 73 | }.bind(this)); 74 | } 75 | 76 | Spotify.prototype.playInContext = function(track, container, context) { 77 | this.bridge.exec("Spotify.ListenTo", {string : track, container : container, context : context}); 78 | } 79 | 80 | module.exports = Spotify; 81 | -------------------------------------------------------------------------------- /modules/samsung_tv/samsung_tv.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var http = require('http'); 4 | var net = require('net'); 5 | 6 | function SamsungTV(data) { 7 | this.host = data.host; 8 | this.mac = data.mac; 9 | this.port = data.port; 10 | 11 | }; 12 | util.inherits(SamsungTV, EventEmitter); 13 | 14 | SamsungTV.PORT = 55000; 15 | 16 | var chr = String.fromCharCode; 17 | 18 | 19 | SamsungTV.prototype.exec = function(command, params) { 20 | var event = command; 21 | this.sendKey("KEY_" + command.toUpperCase()); 22 | }; 23 | 24 | 25 | SamsungTV.prototype.isOn = function (callback) { 26 | var request = http.request({ 27 | host: this.host, port: this.port, path: "/" 28 | }, function(res){ 29 | request.gotResponse = true; 30 | callback(true); 31 | }.bind(this)); 32 | 33 | request.on('socket', function (socket) { 34 | socket.setTimeout(1000); 35 | socket.on('timeout', function() { 36 | if (!request.gotResponse){ 37 | callback(false); 38 | request.abort(); 39 | } 40 | }); 41 | }); 42 | request.on('error', function(e) { callback(false); }); 43 | request.end(); 44 | 45 | } 46 | 47 | 48 | SamsungTV.prototype.sendKey = function (key) { 49 | console.log("* Samsung:", key); 50 | 51 | 52 | var app = "iphone..iapp.samsung"; 53 | var remote = "Network Control"; 54 | var tv = "iphone.LE32C650.iapp.samsung"; 55 | var src = "src"; 56 | var mac = "mac"; 57 | 58 | remote = new Buffer(remote).toString('base64'); 59 | tv = new Buffer(tv).toString('base64'); 60 | src = new Buffer(src).toString('base64'); 61 | mac = new Buffer(mac).toString('base64'); 62 | 63 | var client = net.connect({ 64 | host : this.host, 65 | port : this.port || SamsungTV.PORT, 66 | }, function() { 67 | var msg = chr(0x64, 0x00) + 68 | chr(src.length, 0x00) + src + 69 | chr(mac.length, 0x00) + mac + 70 | chr(remote.length, 0x00) + remote; 71 | var pkt = chr(0x00) + 72 | chr(app.length, 0x00) + app + 73 | chr(msg.length, 0x00) + msg; 74 | 75 | client.write(pkt); 76 | 77 | var msg = chr(0x64) + chr(0x00) + 78 | chr(src.length, 0x00) + src + 79 | chr(mac.length, 0x00) + mac + 80 | chr(remote.length, 0x00) + remote; 81 | var pkt = chr(0x00) + 82 | chr(app.length, 0x00) + app + 83 | chr(msg.length, 0x00) + msg; 84 | 85 | key = new Buffer(key).toString('base64'); 86 | msg = chr(0x00, 0x00, 0x00, key.length, 0x00) + key; 87 | pkt = chr(0x00) + 88 | chr(tv.length, 0x00) + tv + 89 | chr(msg.length, 0x00) + msg; 90 | client.write(pkt); 91 | 92 | client.end(); 93 | }.bind(this)); 94 | 95 | client.setEncoding("ascii"); 96 | client.setTimeout(10); 97 | client.on('data', function(){}); 98 | client.on('end', function(){}); 99 | client.on('error', function(e){ 100 | console.log("! SamsungTV", e); 101 | }); 102 | } 103 | 104 | module.exports = SamsungTV; -------------------------------------------------------------------------------- /modules/sun/sun.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var SunCalc = require("suncalc"); 4 | 5 | function Sun(data) { 6 | this.debug = data.debug; 7 | this.latitude = data.latitude; 8 | this.longitude = data.longitude; 9 | setTimeout(this.calculateSunEvents.bind(this), 1000); 10 | }; 11 | util.inherits(Sun, EventEmitter); 12 | 13 | Sun.TIMEARRAY = ["NightEnd", "NauticalDawn", "Dawn", "Sunrise", "SunriseEnd", "GoldenHourEnd", "SolarNoon", "GoldenHour", "SunsetStart", "Sunset", "Dusk", "NauticalDusk", "Night"]; 14 | 15 | function setDateTimeout(fn, d){ 16 | var t = d.getTime() - (new Date()).getTime(); 17 | if (t > 0) return setTimeout(fn, t); 18 | } 19 | 20 | function pad(str, char, len) { 21 | return str + new Array(Math.max(len - str.length, 0)).join(char); 22 | } 23 | 24 | Sun.prototype.calculateSunEvents = function() { 25 | var times = SunCalc.getTimes(new Date(), this.latitude, this.longitude); 26 | times = this.normalizeStrings(times); 27 | 28 | var position = SunCalc.getPosition(new Date(), this.latitude, this.longitude); 29 | 30 | this.emit("StateEvent", {SunEvents:times}); 31 | this.emit("StateEvent", {SunEvent:this.getSunEvent(new Date(), times)}); 32 | this.emit("StateEvent", {SunPosition:position}); 33 | 34 | var logstring = ""; 35 | for (var i = 0; i < Sun.TIMEARRAY.length; i++) { 36 | var attrname = Sun.TIMEARRAY[i]; 37 | logstring += times[attrname].getHours() + ":" + times[attrname].getMinutes() + " " + attrname + ", "; 38 | setDateTimeout(this.processSunEvent.bind(this, attrname), times[attrname]); 39 | 40 | if (this.debug) { 41 | console.log("SunEvent: " + attrname); 42 | console.log("Time: " + times[attrname]); 43 | console.log("Time now: " + new Date()); 44 | console.log("Time left: " + (times[attrname].getTime() - (new Date()).getTime()).toString()); 45 | } 46 | } 47 | 48 | if (this.debug) console.log("* Sun: Sun events: " + logstring) 49 | 50 | // recalculate tomorrow at midnight; 51 | var tomorrow = new Date(); 52 | tomorrow.setDate(tomorrow.getDate() + 1); 53 | tomorrow.setHours(3); 54 | tomorrow.setMinutes(0); 55 | tomorrow.setSeconds(0); 56 | setDateTimeout(this.calculateSunEvents.bind(this), tomorrow); 57 | } 58 | 59 | Sun.prototype.getSunEvent = function (date, sunEvents) { 60 | var sunEvent = "Night"; 61 | for (var i = 0; i < Sun.TIMEARRAY.length; i++) { 62 | var attrname = Sun.TIMEARRAY[i]; 63 | if (sunEvents[attrname] < date) { 64 | sunEvent = attrname; 65 | } 66 | } 67 | 68 | return sunEvent; 69 | } 70 | 71 | Sun.prototype.normalizeStrings = function(obj) { 72 | var newObj = {}; 73 | for (i in obj) { 74 | newObj[i.charAt(0).toUpperCase() + i.slice(1)] = obj[i]; 75 | } 76 | return newObj; 77 | } 78 | 79 | Sun.prototype.processSunEvent = function (type) { 80 | this.emit("DeviceEvent", type); 81 | this.emit("StateEvent", {SunEvent:type}); 82 | if (this.debug) { 83 | console.log(type); 84 | console.log(new Date()); 85 | } 86 | } 87 | 88 | Sun.prototype.exec = function(command, data) { 89 | console.log("Sun:" + command); 90 | }; 91 | 92 | module.exports = Sun; -------------------------------------------------------------------------------- /examples/cambridge/web/volume.js: -------------------------------------------------------------------------------- 1 | function Volume(on, socket, parentNode) { 2 | this.node = createElement("div"); 3 | this.on = on; 4 | this.socket = socket; 5 | 6 | if (Volume.CSS_APPENDED != true) { 7 | var css = document.createElement("style"); 8 | css.type = "text/css"; 9 | css.innerHTML = Volume.CSS; 10 | document.body.appendChild(css); 11 | Volume.CSS_APPENDED = true; 12 | } 13 | 14 | this.node = createElement("div", "volume", parentNode); 15 | this.nodeSlider = createElement("input", "", this.node); 16 | this.nodeSlider.type = "range"; 17 | this.nodeSlider.min = "0"; 18 | this.nodeSlider.max = "80"; 19 | this.nodeSlider.addEventListener("change", this.handleSlider.bind(this), false); 20 | 21 | this.nodeDB = createElement("div", "volume-db", this.node); 22 | 23 | this.socket.on(this.on, this.handleVolume.bind(this)); 24 | this.socket.emit("getState", this.on); 25 | } 26 | 27 | Volume.CSS = " \ 28 | .volume { \ 29 | position: relative; \ 30 | margin-bottom:1px; \ 31 | margin-top:0px; \ 32 | padding:0; \ 33 | margin-left:0; \ 34 | width: 300px; \ 35 | text-align: center; \ 36 | overflow:hidden; \ 37 | } \ 38 | .volume input[type='range'] { \ 39 | -webkit-appearance: none; \ 40 | border-radius:0px; \ 41 | height:42px; \ 42 | background-color:#222; \ 43 | width:300px; \ 44 | padding:0; \ 45 | margin:0; \ 46 | } \ 47 | .volume input[type='range']:focus { \ 48 | outline: none; \ 49 | } \ 50 | .volume-db { \ 51 | position:absolute; \ 52 | top:0px; \ 53 | color: #666; \ 54 | padding-right:8px; \ 55 | padding-top:6px; \ 56 | height: 42px; \ 57 | border-right:1px solid #555; \ 58 | font-family:Noto Sans, helvetica, sans-serif; \ 59 | font-size:21px; \ 60 | } \ 61 | .volume input[type='range']::-webkit-slider-thumb { \ 62 | -webkit-appearance: none; \ 63 | border-radius: 3px; \ 64 | width:64px; \ 65 | height:42px; \ 66 | opacity:0; \ 67 | box-sizing:border-box; \ 68 | box-shadow:inset 0px 0px 1px white, 0px 1px 2px 1px rgba(0, 0, 0, 0.6); \ 69 | background-image:-webkit-linear-gradient(top, #f5f6f6 0%, #c7d3e1 100%); \ 70 | } \ 71 | "; 72 | 73 | Volume.prototype.updateStyle = function() { 74 | value = (this.nodeSlider.value - this.nodeSlider.min) / 75 | (this.nodeSlider.max - this.nodeSlider.min); 76 | this.nodeSlider.style.backgroundImage = [ 77 | '-webkit-gradient(', 78 | 'linear, ', 79 | 'left top, ', 80 | 'right top, ', 81 | 'color-stop(' + value + ', #333), ', // match the inactive stereo controls 82 | 'color-stop(' + value + ', rgba(0, 0, 0, 0))', 83 | ')' 84 | ].join(''); 85 | this.nodeDB.style.right = 300 - (300 * value); 86 | this.nodeDB.innerHTML = -(this.nodeSlider.max - this.nodeSlider.value);//parseInt(value * 100); 87 | }; 88 | 89 | Volume.prototype.handleVolume = function(data) { 90 | console.log(data); 91 | this.nodeSlider.value = data.volume; 92 | this.updateStyle(); 93 | }; 94 | 95 | Volume.prototype.handleSlider = function() { 96 | var vol = parseInt(this.nodeSlider.value); 97 | this.socket.emit("DeviceEvent", this.on + "." + vol); 98 | this.updateStyle(); 99 | }; 100 | -------------------------------------------------------------------------------- /modules/isy/isy.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var url = require('url'); 4 | var http = require('http'); 5 | var https = require('https'); 6 | var restler = require('restler'); 7 | 8 | // TODO get ISY devices from ISY 9 | // TODO get ISY programs from ISY 10 | // TODO get ISY Hue network resources(consider not going through ISY for hue) 11 | 12 | /** 13 | * Initializes ISY module with username and password for REST requests 14 | * Also adds host of ISY as well as debug data 15 | */ 16 | function Isy(data) { 17 | this.host = data.host; 18 | this.username = data.username; 19 | this.password = data.password; 20 | this.debug = data.debug; 21 | this.devices = data.devices || {}; 22 | 23 | // Create reverse devices map. 24 | this.device_ids = {}; 25 | for (var name in this.devices) { 26 | var id = this.devices[name]; 27 | this.device_ids[id] = name; 28 | } 29 | }; 30 | util.inherits(Isy, EventEmitter); 31 | 32 | /** 33 | * Retrieves nodes from ISY which includes lights, switches etc. 34 | */ 35 | Isy.prototype.getNotes = function() { 36 | 37 | } 38 | 39 | /** 40 | * Uses the first part of command as the @device_name 41 | * Uses the second part of command as the @command_name 42 | * Uses the third part as level if it exists 43 | * Everything else will be transferred as params 44 | */ 45 | Isy.prototype.exec = function(command, params) { 46 | console.log("* Isy Executing: [" + command + "] : " + JSON.stringify(params)); 47 | 48 | var segments = command.split("."); 49 | var device_name = segments.shift(); 50 | var command_name = segments.shift(); 51 | var level = segments.shift(); 52 | this.sendCommand(device_name, command_name, level, params); 53 | }; 54 | 55 | /** 56 | * Map Automaton human readable commands to ISY command names 57 | */ 58 | Isy.prototype.sendCommand = function(device_name, command_name, level, params) { 59 | 60 | switch (command_name) { 61 | case "On": 62 | this.sendRestCommand(this.devices[device_name], "DON", level, params); 63 | break; 64 | case "Off": 65 | this.sendRestCommand(this.devices[device_name], "DOF", level, params); 66 | break; 67 | } 68 | 69 | }; 70 | 71 | /** 72 | * Assemble actual REST comand to send to ISY. Supports basic light commands 73 | * and level comands for lights. 74 | */ 75 | Isy.prototype.sendRestCommand = function(deviceAddress, command, level, parameter) { 76 | var uriToUse = 'http://'+this.host+'/rest/nodes/'+deviceAddress+'/cmd/'+command; 77 | 78 | if(level != null) { 79 | uriToUse += '/' + level; 80 | } 81 | 82 | if (this.debug) console.log("D Isy sending command: "+uriToUse); 83 | 84 | var options = { 85 | username: this.username, 86 | password: this.password 87 | } 88 | 89 | restler.get(uriToUse, options).on('complete', function(data, response) { 90 | if(response.statusCode == 200) { 91 | console.log("* Isy Success: Command executed"); 92 | } else { 93 | console.log("! Isy Failure: REST error"); 94 | } 95 | }); 96 | } 97 | 98 | 99 | Isy.prototype.log = function(data) { 100 | console.log("Isy LOG:" + data); 101 | this.emit("DeviceEvent", "Logged"); 102 | }; 103 | 104 | module.exports = Isy; -------------------------------------------------------------------------------- /modules/netatmo/netatmo.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var netatmo = require('node-netatmo'); 4 | 5 | function Netatmo(data) { 6 | this.host = data.host; 7 | this.debug = data.debug; 8 | this.connection = new netatmo.Netatmo(); 9 | this.connection.logger.info = function(msg, props) { console.log("* Netatmo:", msg); if (props) console.log(props); } 10 | this.connection.on('error', function(err) { console.log("! Netatmo error", err);}) 11 | this.connection.setConfig( data.clientId , data.clientSecret , data.userName , data.password); 12 | this.connection.getToken(function(err) { 13 | if (err) return console.log('! Netatmo error: getToken: ' + err.message); 14 | if (this.debug) console.log("* Netatmo logged in"); 15 | // good to go! 16 | this.check() 17 | }.bind(this)) 18 | } 19 | util.inherits(Netatmo, EventEmitter); 20 | 21 | Netatmo.prototype.check = function() { 22 | this.connection.getUser(function(err, results) { 23 | if (err) return console.log('! Netatmo error: getUser: ' + err.message); 24 | if (results.status !== 'ok') { console.log('getUser not ok'); return console.log(results); } 25 | if (this.debug) console.log("D Netatmo user:", results.body); 26 | this.devices = results.body.devices; 27 | }.bind(this)); 28 | this.connection.getDevices(function(err, results) { 29 | if (err) return console.log('! Netatmo error: getDevices: ' + err.message); 30 | if (results.status !== 'ok') { console.log('getDevices not ok'); return console.log(results); } 31 | if (this.debug) console.log("D Netatmo devices:", results.body); 32 | this.devices = results.body.devices; 33 | this.deviceId = this.devices[0]._id; 34 | this.modules = results.body.modules; 35 | this.getMeasurements(); 36 | }.bind(this)); 37 | } 38 | 39 | Netatmo.TYPES = [ 'temperature', 'humidity', 'co2', 'pressure', 'noise' ]; 40 | Netatmo.prototype.getMeasurements = function() { 41 | var allDevices = this.devices.concat(this.modules); 42 | for (var i in allDevices) { 43 | var device = allDevices[i]; 44 | 45 | if (device.type == "NAModule3") { // Ignore rain modules for now 46 | continue; 47 | } 48 | var params = { 49 | device_id : this.deviceId, 50 | module_id : device._id, 51 | scale : 'max', 52 | type : Netatmo.TYPES, 53 | date_end : "last", 54 | }; 55 | var name = device.module_name; 56 | this.connection.getMeasurement(params, function(device, err, results) { 57 | if (err) return console.log('! Netatmo error: getMeasurement: ', name, err.message); 58 | if (results.status !== 'ok') { 59 | console.log('! NETATMO ERROR:', name, results); 60 | return; 61 | } 62 | var measurements = results.body[0].value[0]; 63 | var status = {} 64 | for (var i = 0; i < measurements.length; i++) { 65 | if (measurements[i]) status[Netatmo.TYPES[i]] = measurements[i]; 66 | }; 67 | status.temperature = Math.round(status.temperature * 9 / 5 + 32); 68 | this.emit("DeviceEvent", device.module_name + "." + status.temperature, status); 69 | var state = {}; 70 | state["Netatmo." + device.module_name] = status; 71 | this.emit("StateEvent", state); 72 | }.bind(this, device)); 73 | } 74 | setTimeout(this.getMeasurements.bind(this), 5 * 60 * 60 * 1000); 75 | } 76 | 77 | module.exports = Netatmo; 78 | -------------------------------------------------------------------------------- /examples/tools/voice.py: -------------------------------------------------------------------------------- 1 | from win32com.client import constants 2 | import win32com.client 3 | import pythoncom 4 | 5 | """Sample code for using the Microsoft Speech SDK 5.1 via COM in Python. 6 | Requires that the SDK be installed; it's a free download from 7 | http://microsoft.com/speech 8 | and that MakePy has been used on it (in PythonWin, 9 | select Tools | COM MakePy Utility | Microsoft Speech Object Library 5.1). 10 | 11 | After running this, then saying "One", "Two", "Three" or "Four" should 12 | display "You said One" etc on the console. The recognition can be a bit 13 | shaky at first until you've trained it (via the Speech entry in the Windows 14 | Control Panel.""" 15 | class SpeechRecognition: 16 | """ Initialize the speech recognition with the passed in list of words """ 17 | def __init__(self, wordsToAdd): 18 | # For text-to-speech 19 | self.speaker = win32com.client.Dispatch("SAPI.SpVoice") 20 | # For speech recognition - first create a listener 21 | self.listener = win32com.client.Dispatch("SAPI.SpSharedRecognizer") 22 | # Then a recognition context 23 | self.context = self.listener.CreateRecoContext() 24 | # which has an associated grammar 25 | self.grammar = self.context.CreateGrammar() 26 | # Do not allow free word recognition - only command and control 27 | # recognizing the words in the grammar only 28 | self.grammar.DictationSetState(0) 29 | # Create a new rule for the grammar, that is top level (so it begins 30 | # a recognition) and dynamic (ie we can change it at runtime) 31 | self.wordsRule = self.grammar.Rules.Add("wordsRule", 32 | constants.SRATopLevel + constants.SRADynamic, 0) 33 | # Clear the rule (not necessary first time, but if we're changing it 34 | # dynamically then it's useful) 35 | self.wordsRule.Clear() 36 | # And go through the list of words, adding each to the rule 37 | [ self.wordsRule.InitialState.AddWordTransition(None, word) for word in wordsToAdd ] 38 | # Set the wordsRule to be active 39 | self.grammar.Rules.Commit() 40 | self.grammar.CmdSetRuleState("wordsRule", 1) 41 | # Commit the changes to the grammar 42 | self.grammar.Rules.Commit() 43 | # And add an event handler that's called back when recognition occurs 44 | self.eventHandler = ContextEvents(self.context) 45 | # Announce we've started using speech synthesis 46 | self.say("Started successfully") 47 | """Speak a word or phrase""" 48 | def say(self, phrase): 49 | self.speaker.Speak(phrase) 50 | 51 | 52 | """The callback class that handles the events raised by the speech object. 53 | See "Automation | SpSharedRecoContext (Events)" in the MS Speech SDK 54 | online help for documentation of the other events supported. """ 55 | class ContextEvents(win32com.client.getevents("SAPI.SpSharedRecoContext")): 56 | """Called when a word/phrase is successfully recognized - 57 | ie it is found in a currently open grammar with a sufficiently high 58 | confidence""" 59 | def OnRecognition(self, StreamNumber, StreamPosition, RecognitionType, Result): 60 | newResult = win32com.client.Dispatch(Result) 61 | print "You said: ",newResult.PhraseInfo.GetText() 62 | 63 | if __name__=='__main__': 64 | wordsToAdd = [ "One", "Two", "Three", "Four" ] 65 | speechReco = SpeechRecognition(wordsToAdd) 66 | while 1: 67 | pythoncom.PumpWaitingMessages() -------------------------------------------------------------------------------- /modules/telnet/telnet.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | 5 | /* 6 | var n = new (require("./telnet.js")).Telnet({ 7 | host : "10.0.1.20", 8 | commands : { 9 | "MacMini" : "GAME", 10 | "Sonos" : "DVR" 11 | } 12 | }); 13 | */ 14 | 15 | /** 16 | * Generic telnet command sender for Route 17 | */ 18 | function Telnet(data) { 19 | this.host = data.host; 20 | this.port = data.port || 23; 21 | this.debug = data.debug; 22 | 23 | // Map of source names to ids. 24 | this.commands = data.commands ? data.commands : {}; 25 | 26 | // Create reverse commands map - you'd do this to make Telnet 27 | // emit specific events when it sees certain commands (not implemented) 28 | this.commandIds = {}; 29 | for (var name in this.commands) { 30 | var id = this.commands[name]; 31 | this.commandIds[id] = name; 32 | } 33 | 34 | this.commandQueue = []; 35 | this.connect(); 36 | }; 37 | util.inherits(Telnet, EventEmitter); 38 | 39 | Telnet.prototype.send = function(string) { 40 | var isFirstRequest = (this.commandQueue.length == 0); 41 | this.commandQueue.push(string); 42 | if (isFirstRequest) 43 | setTimeout(this.sendNextCommand.bind(this), 10); // nextTick is not behaving correctly 44 | }; 45 | 46 | Telnet.prototype.sendNextCommand = function() { 47 | if (!this.commandQueue.length) return; 48 | var string = this.commandQueue.shift(); 49 | if (this.debug) console.log("> ", string); 50 | 51 | // TODO: This should likely allow waiting for a response to trigger the next command 52 | this.client.write(string + "\r", "UTF8", function () { 53 | setTimeout(this.sendNextCommand.bind(this), 300); 54 | }.bind(this)); 55 | }; 56 | 57 | Telnet.prototype.exec = function(command) { 58 | console.log("* Telnet Executing: " + command); 59 | 60 | var segments = command.split("."); 61 | 62 | var action = segments.shift(); 63 | if (this.commands[action]) { 64 | this.send(this.commands[action]); 65 | this.emit("DeviceEvent", command); 66 | } 67 | }; 68 | 69 | Telnet.prototype.parseData = function(data) { 70 | if (this.debug) console.log("Telnet", data); 71 | // TODO emit event if data matches a command 72 | } 73 | 74 | // Connection 75 | Telnet.prototype.connect = function() { 76 | this.reconnecting_ = false; 77 | this.client = net.connect({ 78 | host : this.host, 79 | port : this.port 80 | }); 81 | 82 | this.client.on('data', this.handleData.bind(this)); 83 | this.client.on('error', this.handleError.bind(this)); 84 | this.client.on('close', this.handleClose.bind(this)); 85 | }; 86 | 87 | Telnet.prototype.reconnect = function() { 88 | if (this.reconnecting_) return; 89 | 90 | this.reconnecting_ = true; 91 | setTimeout(this.connect.bind(this), 1000); 92 | }; 93 | 94 | Telnet.prototype.handleData = function(data) { 95 | data = (data + "").trim(); 96 | this.parseData(data.split("\r\n").shift()); 97 | }; 98 | 99 | Telnet.prototype.handleError = function(e) { 100 | console.log("! Telnet\t" + e); 101 | 102 | }; 103 | 104 | Telnet.prototype.handleClose = function(e) { 105 | setTimeout(this.reconnect.bind(this), 10000); 106 | }; 107 | 108 | Telnet.prototype.handleEnd = function() { 109 | this.emit("sourceEvent", "Telnet.Disconnected"); 110 | this.reconnect(); 111 | }; 112 | 113 | Telnet.prototype.log = function(data) { 114 | console.log("Telnet LOG:" + data); 115 | this.emit("sourceEvent", "Logged"); 116 | } 117 | 118 | module.exports = Telnet; 119 | -------------------------------------------------------------------------------- /examples/cambridge/web/stereo.js: -------------------------------------------------------------------------------- 1 | function Stereo(components, socket, parentNode) { 2 | this.components = components; 3 | this.socket = socket; 4 | 5 | if (Stereo.CSS_APPENDED == false) { 6 | var css = document.createElement("style"); 7 | css.type = "text/css"; 8 | css.innerHTML = Stereo.CSS 9 | document.body.appendChild(css); 10 | Stereo.CSS_APPENDED = true; 11 | } 12 | 13 | this.node = createElement('div', 'stereo', parentNode); 14 | 15 | // Components highlight based on activation of their 'on' command 16 | // however, getState causes old messages to arrive. We figure out 17 | // which was the most recent based on the data, which is the time 18 | // the item was activated. TODO: make all states have time. 19 | this.lastUpdated = 0; 20 | for (var i = 0, component; component = components[i]; i++) { 21 | var c = new Component(component, i, this); 22 | this.node.appendChild(c.node); 23 | component.component = c; 24 | } 25 | } 26 | 27 | Stereo.CSS = " \ 28 | .stereo { \ 29 | position:relative; \ 30 | z-index:1; \ 31 | display:inline-block; \ 32 | width: 300px; \ 33 | height: 64px; \ 34 | transition:all 0.1s; \ 35 | background-color:#222; \ 36 | margin-bottom:1px; \ 37 | } \ 38 | .stereo-component { \ 39 | position:absolute; \ 40 | top:0px; \ 41 | height: 64px; \ 42 | box-sizing:border-box; \ 43 | padding-top:8px; \ 44 | color:#666; \ 45 | font-family: helvetica, arial, sans-serif; \ 46 | font-size:14px; \ 47 | font-weight:bold; \ 48 | text-align:center; \ 49 | background-repeat:no-repeat; \ 50 | background-position: center center; \ 51 | opacity:0.2; \ 52 | -webkit-transition:all 0.15s; \ 53 | cursor:pointer; \ 54 | } \ 55 | .stereo-component:hover { \ 56 | opacity:0.4; \ 57 | } \ 58 | .stereo-component.on { \ 59 | opacity:1; \ 60 | background-color:#333; \ 61 | } \ 62 | .stereo-component.on:hover { \ 63 | opacity:1; \ 64 | } \ 65 | "; 66 | Stereo.CSS_APPENDED = false; 67 | 68 | Stereo.prototype.handleOn = function(component, time) { 69 | if (time < this.lastUpdated) return; 70 | 71 | this.focus(component); 72 | this.lastUpdated = time; 73 | }; 74 | 75 | Stereo.prototype.focus = function(component) { 76 | if (this.focused) this.focused.unfocus(); 77 | 78 | this.focused = component; 79 | this.focused.focus(); 80 | }; 81 | 82 | Stereo.prototype.handleClick = function(component) { 83 | this.socket.emit("DeviceEvent", [component.action].join(".")); 84 | }; 85 | 86 | function Component(data, index, parent) { 87 | this.node = createElement("div", "stereo-component", parent.node, ""); 88 | this.action = data.action; 89 | this.parent = parent; 90 | this.on = data.on; 91 | this.socket = parent.socket; 92 | 93 | if (this.on) 94 | this.socket.on(this.on, this.handleOn.bind(this)); 95 | this.socket.emit("getState", this.on); 96 | 97 | var width = (100 / parent.components.length); 98 | this.node.style.width = width + '%'; 99 | this.node.style.left = index * width + '%'; 100 | if (data.image) 101 | this.node.style.backgroundImage = 'url('+data.image+')'; 102 | else 103 | this.node.appendChild(document.createTextNode(data.name)); 104 | this.node.addEventListener("click", this.handleClick.bind(this), false); 105 | }; 106 | 107 | Component.prototype.handleClick = function() { 108 | this.parent.handleClick(this); 109 | }; 110 | 111 | Component.prototype.handleOn = function(data) { 112 | this.parent.handleOn(this, data.stateUpdatedTime); 113 | }; 114 | 115 | Component.prototype.focus = function() { 116 | this.node.classList.add('on'); 117 | }; 118 | 119 | Component.prototype.unfocus = function() { 120 | this.node.classList.remove('on'); 121 | }; 122 | -------------------------------------------------------------------------------- /modules/web/web_express.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var express = require('express'); 3 | var io = require('socket.io'); 4 | var https = require('https'); 5 | var http = require('http'); 6 | var util = require('util'); 7 | var url = require('url'); 8 | 9 | function Web(data) { 10 | this.app = express(); 11 | 12 | // Override handlers and directories 13 | for (var path in data.handlers) this.app.all(path, data.handlers[path]); 14 | for (var path in data.dirs) this.app.use(path, express.static(data.dirs[path])); 15 | 16 | // Redirect root requests 17 | if (data.rootRedirect) this.app.all('/', function (req, res) { 18 | res.redirect(data.rootRedirect); 19 | }.bind(this)); 20 | 21 | this.app.use('/event', this.handleEventReq.bind(this)); 22 | this.app.use('/state', this.handleStateReq.bind(this)); 23 | this.app.use(express.static(data.dir)); // Default directory 24 | 25 | // Set up HTTP 26 | this.server = http.createServer(this.app).listen(data.port || 8080); 27 | this.socket = io.listen(this.server, { log: false }); 28 | this.socket.on('connection', this.handleSocketConnection.bind(this)); 29 | this.socket.on('error', this.handleSocketError.bind(this)); 30 | // TODO: Set up Event-only port, if auth doesn't work out. // if (data.eventPort) this.eventServer = http.createServer(this.handleEventReq.bind(this)).listen(data.eventPort); 31 | 32 | // Set up HTTPS if requested 33 | if (data.securePort && data.key && data.cert) { 34 | var options = { key: data.key, cert: data.cert, ca: data.ca } 35 | this.password = data.password; 36 | this.secureServer = https.createServer(options, this.app).listen(data.securePort); 37 | this.secureSocket = io.listen(this.secureServer, { log: false }); 38 | this.secureSocket.on('connection', this.handleSocketConnection.bind(this)); 39 | this.secureSocket.on('error', this.handleSocketError.bind(this)); 40 | } 41 | 42 | this.clients = []; 43 | }; 44 | util.inherits(Web, EventEmitter); 45 | 46 | // Emit requests to /event/pie?params as 'Web.pie?params' 47 | Web.prototype.handleEventReq = function(req, res) { 48 | var info = url.parse(req.url, true); 49 | info.pathname = info.pathname.substring(1); 50 | this.handleEvent(info); 51 | res.sendStatus(200); 52 | } 53 | 54 | Web.prototype.handleEvent = function(info) { 55 | this.emit("DeviceEvent", info.pathname, info.query); 56 | } 57 | 58 | Web.prototype.handleSocketConnection = function(socket) { 59 | this.clients.push(socket); 60 | if (this.state) socket.emit('state', this.state.allValues()); 61 | socket.on('message', this.handleSocketMessage.bind(this)); 62 | socket.on('error', this.handleSocketError.bind(this)); 63 | socket.on('disconnect', this.handleSocketClose.bind(this, socket)); 64 | }; 65 | 66 | Web.prototype.handleSocketError = function(socket) { 67 | console.log("! Web socket error", socket); 68 | }; 69 | 70 | Web.prototype.handleSocketMessage = function(message) { 71 | this.handleEvent(url.parse(message, true)); 72 | }; 73 | 74 | Web.prototype.handleSocketClose = function(socket) { 75 | this.clients = this.clients.filter(function(c) { return c != socket; }); 76 | }; 77 | 78 | // State Changes 79 | 80 | Web.prototype.handleStateReq = function (req, res) { 81 | res.json(this.state.allValues()); 82 | } 83 | 84 | Web.prototype.setStateObject = function(object) { 85 | this.state = object; 86 | this.state.addListener('StateEvent', this.stateChanged.bind(this)); 87 | }; 88 | 89 | Web.prototype.stateChanged = function(newState) { 90 | this.clients.forEach(function(c) { c.emit('state', newState); }); 91 | }; 92 | 93 | Web.prototype.customStateChanged = function(stateName, newState) { 94 | this.clients.forEach(function(c) { c.emit(stateName, newState); }); 95 | }; 96 | 97 | module.exports = Web; 98 | -------------------------------------------------------------------------------- /modules/fitbit/fitbit.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var url = require('url'); 4 | var util = require('util'); 5 | var oauth = require('oauth'); 6 | var xml2js = require('xml2js'); 7 | var storage = require('../storage'); 8 | 9 | Fitbit.PORT = 9040; 10 | function Fitbit(data) { 11 | this.listenPort = data.listenPort || Fitbit.PORT; 12 | this.server = http.createServer(this.handleReq.bind(this)).listen(this.listenPort); 13 | this.users = data.users; 14 | this.oauth = new oauth.OAuth( '', '', data.token, data.secret, '1.0', null, 'HMAC-SHA1'); 15 | this.debug = data.debug; 16 | this.storage = new storage("Fitbit"); 17 | 18 | var lastcheck = new Date(this.storage.getItem("lastCheck")); 19 | lastcheck = new Date() - lastcheck; 20 | this.storage.setItem("lastCheck", new Date()); 21 | 22 | if (lastcheck > 10 * 60 * 1000) { 23 | for (var user in this.users) { 24 | this.updateCollection(user, "activities"); 25 | this.updateCollection(user, "body"); 26 | this.updateCollection(user, "sleep"); 27 | this.updateCollection(user, "heart"); 28 | this.fetchSleepInfo(user); 29 | } 30 | } 31 | } 32 | util.inherits(Fitbit, EventEmitter); 33 | 34 | Fitbit.prototype.handleReq = function(req, res) { 35 | console.log(req.url); 36 | var info = url.parse(req.url, true); 37 | if (req.method == 'POST') { 38 | var data = new Buffer(''); 39 | req.on('data', function(chunk) { data = Buffer.concat([data, chunk]);}); 40 | req.on('end', function() { 41 | var string = data.toString('utf-8'); 42 | string = string.split("\r\n\r\n").pop(); 43 | string = string.split("\r\n--").shift(); 44 | var updates = JSON.parse(string); 45 | res.writeHead(204, {}); 46 | res.end(); 47 | this.handleUpdates(updates); 48 | }.bind(this)); 49 | } else { 50 | res.writeHead(204, {}); 51 | res.end(); 52 | } 53 | }; 54 | 55 | Fitbit.prototype.updateCollection = function(userId, type) { 56 | var date = new Date(); 57 | date = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join("-") 58 | var user = this.users[userId]; 59 | var url = "http://api.fitbit.com/1/user/-/" + type + "/date/" + date + ".json"; 60 | this.oauth.get(url, user.token, user.secret, function (err, data, response) { 61 | var state = {}; 62 | try{ 63 | state["fitbit." + userId + "." + type] = JSON.parse(data); 64 | } catch (e) { 65 | console.log("Could not parse fitbit", url, err, data, response ? response.statusCode : ""); 66 | } 67 | if (this.debug) console.log(JSON.stringify(state, null, ' ')); 68 | this.emit("StateEvent", state); 69 | }.bind(this)); 70 | } 71 | 72 | 73 | Fitbit.prototype.fetchSleepInfo = function(userId) { 74 | var user = this.users[userId]; 75 | var url = "http://api.fitbit.com/1/user/-/sleep/minutesAsleep/date/today/1w.json"; 76 | this.oauth.get(url, user.token, user.secret, function (err, data, response) { 77 | var state = {}; 78 | try{ 79 | state["fitbit." + userId + "." + "SleepInfo"] = JSON.parse(data); 80 | } catch (e) { 81 | console.log("Could not parse fitbit", url, err, data, response.statusCode); 82 | } 83 | if (this.debug) console.log(JSON.stringify(state, null, ' ')); 84 | this.emit("StateEvent", state); 85 | }.bind(this)); 86 | } 87 | 88 | 89 | 90 | Fitbit.prototype.handleUpdates = function(updates) { 91 | for (var i = 0; i < updates.length; i++) { 92 | var update = updates[i]; 93 | var user = update.subscriptionId; 94 | var type = update.collectionType 95 | console.log("* Fitbit updated", user, type); 96 | this.updateCollection(user, type); 97 | }; 98 | } 99 | 100 | 101 | 102 | 103 | 104 | 105 | module.exports = Fitbit; 106 | -------------------------------------------------------------------------------- /modules/bt-proximity/bt-proximity_experimental.js: -------------------------------------------------------------------------------- 1 | var noble = require('noble'), 2 | util = require('util'), 3 | EventEmitter = require('events').EventEmitter; 4 | 5 | function BTProximity(data) { 6 | this.debug = data.debug; 7 | noble.on('stateChange', this.handleStateChange.bind(this)); 8 | noble.on('discover', this.handleDiscover.bind(this)); 9 | 10 | // Map of people names to macs. 11 | this.people = data.people || {}; 12 | 13 | // Create reverse people map. 14 | this.people_ids = {}; 15 | for (var name in this.people) { 16 | var id = this.people[name]; 17 | this.people_ids[id] = name; 18 | } 19 | 20 | // Create list of present ids 21 | this.present_ids = []; 22 | 23 | this.lastSeen = {} 24 | 25 | if(this.debug) console.log(this.people) 26 | 27 | setInterval(this.checkAway.bind(this), 5000); 28 | } 29 | util.inherits(BTProximity, EventEmitter); 30 | 31 | BTProximity.AWAYAFTERTIME = 90000; // how long we can go without seeing a device before we consider you away 32 | 33 | BTProximity.prototype.handleStateChange = function(state) { 34 | console.log(state); 35 | if (state === "poweredOn") { 36 | console.log("BTProximity: Powered on"); 37 | this.init(); 38 | } 39 | }; 40 | 41 | BTProximity.prototype.exec = function(command, data) { 42 | }; 43 | 44 | BTProximity.prototype.init = function() { 45 | 46 | console.log("Initing..."); 47 | noble.startScanning([], true); 48 | 49 | /*if (!this.lastSeen) { 50 | this.lastSeen = new Date().getTime(); // Pretend we're here. 51 | this.present = true; 52 | }*/ 53 | }; 54 | 55 | BTProximity.prototype.shutdown = function() { 56 | }; 57 | 58 | BTProximity.prototype.checkAway = function() { 59 | 60 | for (var mac in this.lastSeen) { 61 | 62 | //console.log("Last seen time: "+this.lastSeen[mac]); 63 | if (this.lastSeen[mac] + BTProximity.AWAYAFTERTIME < new Date().getTime()) { 64 | this.setAway(mac); 65 | } 66 | } 67 | }; 68 | 69 | BTProximity.prototype.setAway = function(mac) { 70 | 71 | // If we still have mac as present, remove it now 72 | for (var i = 0; i < this.present_ids.length; i++) { 73 | if (mac == this.present_ids[i]) { 74 | this.present_ids.splice(i,1); 75 | if (this.debug) { 76 | console.log("BTProximity: User "+ this.people_ids[mac] +" is away " + (new Date()).toLocaleTimeString()); 77 | } 78 | this.emit("DeviceEvent", "Away." + this.people_ids[mac] || mac); 79 | } 80 | } 81 | }; 82 | 83 | BTProximity.prototype.setPresent = function(mac, peripheral) { 84 | var name = (this.people_ids[mac] || peripheral.advertisement.localName) 85 | 86 | var now = new Date().getTime(); 87 | var elapsed = (now - this.lastSeen[mac]) / 1000; 88 | //if ((now - this.lastSeen[mac]) < 100) return; 89 | //console.log(mac, name, peripheral.rssi + 100, elapsed); 90 | 91 | // Update the last time we saw this id 92 | this.lastSeen[mac] = now 93 | 94 | // If mac is already counted as present, return 95 | for (var id in this.present_ids) { 96 | //if (this.debug) console.log("Ignoring ", peripheral.advertisement.localName, mac, peripheral.rssi) 97 | if (mac == this.present_ids[id]) return; 98 | } 99 | 100 | // Otherwise, add id to present id array 101 | this.present_ids.push(mac); 102 | 103 | if (this.debug) { 104 | console.log("BTProximity: User "+ name +" found ", 105 | (new Date()).toLocaleTimeString(), 106 | mac, 107 | peripheral.rssi, 108 | JSON.stringify(peripheral.advertisement)); 109 | } 110 | if (name) this.emit("DeviceEvent", "Present." + name); 111 | }; 112 | 113 | BTProximity.prototype.handleDiscover = function(peripheral) { 114 | // Convert UUID to MAC 115 | var mac = peripheral.uuid.toUpperCase().replace(/(.{2})(?=.)/g,"$1:"); 116 | 117 | // If the mac we found is in our people list, set person to present 118 | for (var id in this.people_ids) { 119 | if (id == mac) { 120 | this.setPresent(mac, peripheral); 121 | } 122 | } 123 | 124 | if (this.debug) { 125 | this.setPresent(mac, peripheral); 126 | } 127 | }; 128 | 129 | module.exports = BTProximity; 130 | -------------------------------------------------------------------------------- /modules/leap/leap.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var Leapjs = require('leapjs'); 3 | var util = require('util'); 4 | 5 | function Leap(data) { 6 | this.threshold = data.threshold || 10; 7 | 8 | var controllerOptions = { 9 | host: data.host || '127.0.0.1', 10 | port: data.port || 6437, 11 | enableGestures: data.enableGestures || false, 12 | enableHeartbeat: data.enableHeartbeat || true, 13 | heartbeatInterval: data.heartbeatInterval || 100, 14 | requestProtocolVersion: data.requestProtocolVersion || 3 15 | }; 16 | this.controller = new Leapjs.Controller(controllerOptions); 17 | 18 | this.controller.on('frame', this.handleFrame.bind(this)); 19 | 20 | if (controllerOptions.enableGestures) { 21 | this.controller.on('gesture', this.handleGesture.bind(this)); 22 | } 23 | 24 | this.controller.on('ready', function() { 25 | console.log("Leap ready"); 26 | }); 27 | this.controller.on('connect', function() { 28 | console.log("Leap connected"); 29 | }); 30 | this.controller.on('disconnect', function() { 31 | console.log("Leap disconnected"); 32 | }); 33 | this.controller.on('focus', function() { 34 | console.log("Leap focused"); 35 | }); 36 | this.controller.on('blur', function() { 37 | console.log("Leap blurry"); 38 | }); 39 | this.controller.on('deviceConnected', function() { 40 | console.log("Leap Device connected"); 41 | }); 42 | this.controller.on('deviceDisconnected', function() { 43 | console.log("Leap device disconnected"); 44 | }); 45 | 46 | this.controller.connect(); 47 | console.log("\nWaiting for Leap to connect..."); 48 | } 49 | util.inherits(Leap, EventEmitter); 50 | 51 | Leap.frameCount = 0; 52 | Leap.gestureId = 0; 53 | 54 | Leap.prototype.handleFrame = function(frame) { 55 | // console.log(frame); 56 | // this.emit("DeviceEvent", frame); 57 | }; 58 | 59 | Leap.prototype.handleGesture = function(gesture, frame) { 60 | 61 | switch (gesture.state) { 62 | case "start": 63 | console.log("Starting" + gesture.id); 64 | if (Leap.gestureId === 0) { 65 | Leap.gestureId = gesture.id; 66 | Leap.frameCount = 0; 67 | } 68 | break; 69 | case "update": 70 | console.log("Updating" + gesture.id); 71 | if(Leap.gestureId === gesture.id) { 72 | Leap.frameCount++; 73 | } 74 | break; 75 | case "stop": 76 | console.log("Stopping" + gesture.id); 77 | if (Leap.gestureId === gesture.id) { 78 | if (Leap.frameCount >= this.threshold) { 79 | switch (gesture.type) { 80 | case "circle": 81 | if(gesture.clockwiseness === "clockwise") { 82 | this.emit("DeviceEvent", "Circle.Clockwiseness"); 83 | } 84 | else { 85 | this.emit("DeviceEvent", "Circle.CounterClockwiseness"); 86 | } 87 | break; 88 | case "screenTap": 89 | this.emit("DeviceEvent", "ScreenTap"); 90 | break; 91 | case "keyTap": 92 | this.emit("DeviceEvent", "KeyTap"); 93 | break; 94 | case "swipe": 95 | if (Math.sqrt(Math.pow(gesture.direction[0], 2)) > Math.sqrt(Math.pow(gesture.direction[1], 2))) { 96 | //Horizontal gesture 97 | if(gesture.direction[0] < 0) { 98 | // Left gesture 99 | this.emit("DeviceEvent", "Swipe.Left"); 100 | } 101 | else { 102 | // Right gesture 103 | this.emit("DeviceEvent", "Swipe.Right"); 104 | } 105 | } 106 | else { 107 | //Vertical gesture 108 | if(gesture.direction[1] < 0) { 109 | // Downward gesture 110 | this.emit("DeviceEvent", "Swipe.Down"); 111 | } 112 | else { 113 | // Upward gesture 114 | this.emit("DeviceEvent", "Swipe.Up"); 115 | } 116 | } 117 | break; 118 | } 119 | } 120 | Leap.frameCount = 0; 121 | Leap.gestureId = 0; 122 | } 123 | break; 124 | } 125 | }; 126 | 127 | module.exports = Leap; -------------------------------------------------------------------------------- /modules/web/web.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var url = require('url'); 4 | var fs = require('fs'); 5 | var util = require('util'); 6 | var io = require('socket.io'); 7 | var path = require('path'); 8 | 9 | function Web(data) { 10 | this.dir = data.dir; 11 | this.server = http.createServer(this.handleReq.bind(this)).listen(data.port ? data.port : 8000); 12 | 13 | this.username = data.username; 14 | this.password = data.password; 15 | 16 | this.socket = io.listen(this.server, { log: false }); 17 | this.socket.on('connection', this.handleSocketConnection.bind(this)); 18 | this.socket.on('error', this.handleSocketError.bind(this)); 19 | this.clients = []; 20 | 21 | this.state = {}; 22 | 23 | console.log("Web Connected"); 24 | }; 25 | 26 | util.inherits(Web, EventEmitter); 27 | Web.STATEOBSERVER = true; 28 | Web.MIMETYPES = { 29 | "html" : "text/html", 30 | "jpeg" : "image/jpeg", 31 | "jpg" : "image/jpeg", 32 | "png" : "image/png", 33 | "js" : "text/javascript", 34 | "css" : "text/css" 35 | }; 36 | 37 | Web.prototype.exec = function(command, details) { 38 | 39 | }; 40 | 41 | Web.prototype.initStateObserver = function(route, state) { 42 | this.state = state; 43 | route.on("StateChanged", this.handleStateChanged.bind(this)); 44 | }; 45 | 46 | Web.prototype.handleStateChanged = function(state, data) { 47 | for (var i = 0; i < this.clients.length; i++) { 48 | this.clients[i].emit(state, data); 49 | } 50 | }; 51 | 52 | Web.prototype.handleReq = function(req, res) { 53 | var query = url.parse(req.url); 54 | var page = query.pathname.split("/"); 55 | 56 | var header = req.headers['authorization']||''; // get the header 57 | var token = header.split(/\s+/).pop()||''; // and the encoded auth token 58 | var auth = new Buffer(token, 'base64').toString(); // convert from base64 59 | var parts = auth.split(/:/); // split on colon 60 | var username = parts[0]; 61 | var password = parts[1]; 62 | 63 | if (this.username && (this.username != username || this.password != password)) { 64 | console.log("Web: failed login"); 65 | res.writeHead(401, { 66 | 'WWW-Authenticate' : 'Basic realm="login"' 67 | }); 68 | 69 | //res.setHeader('WWW-Authenticate', 'Basic realm="need login"'); 70 | res.end('Authorization required'); 71 | return; 72 | } 73 | 74 | res.writeHead(200, {'Content-Type': 'text/plain'}); 75 | // Emit requests to /event/pie as 'Web.pie' 76 | if (page.length > 2 && page[1] == 'event' && page[2] != "") { 77 | this.emit("DeviceEvent", page[2]); 78 | res.end(); 79 | } else { 80 | var filename = path.join(this.dir, query.pathname); 81 | switch(query.pathname) { 82 | case "/": 83 | filename = path.join(this.dir, "index.html"); 84 | default: 85 | var ext = path.extname(filename).substring(1); 86 | if (fs.existsSync(filename)) { 87 | res.writeHead(200, {'Content-Type': (Web.MIMETYPES[ext] || "text/html")}); 88 | var fileStream = fs.createReadStream(filename); 89 | fileStream.pipe(res); 90 | } else { 91 | res.end(); 92 | } 93 | break; 94 | } 95 | } 96 | }; 97 | 98 | Web.prototype.handleSocketConnection = function(socket) { 99 | this.clients.push(socket); 100 | this.emit("DeviceEvent", "ClientConnected"); 101 | socket.on('getState', this.handleSocketGetState.bind(this, socket)); 102 | socket.on('error', this.handleSocketError.bind(this, socket)); 103 | socket.on("DeviceEvent", this.handleEvent.bind(this)); 104 | socket.on('disconnect', this.handleSocketClose.bind(this, socket)); 105 | }; 106 | 107 | Web.prototype.handleEvent = function(command, details) { 108 | this.emit("DeviceEvent", command, details); 109 | }; 110 | 111 | Web.prototype.handleSocketError = function(socket) { 112 | 113 | }; 114 | 115 | Web.prototype.handleSocketGetState = function(socket, stateName) { 116 | if (stateName in this.state) 117 | socket.emit(stateName, this.state[stateName]); 118 | }; 119 | 120 | Web.prototype.handleSocketClose = function(socket) { 121 | this.emit("DeviceEvent", "ClientDisconnected"); 122 | for (var i = 0; i < this.clients.length; i++) { 123 | if (this.clients[i] == socket) { 124 | this.clients.splice(i, 1); 125 | } 126 | } 127 | }; 128 | 129 | module.exports = Web; 130 | -------------------------------------------------------------------------------- /modules/slimp3/slimp3.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | var dgram = require('dgram'); 5 | 6 | // http://wiki.slimdevices.com/index.php/SLIMP3_client_protocol 7 | 8 | 9 | var CODE_DELAY = 0x00; 10 | var CODE_CMD = 0x02; 11 | var CODE_DATA = 0x03; 12 | 13 | var NUM_LINES = 2; 14 | var LINE_WIDTH = 40; 15 | var NUM_CHARS = NUM_LINES * LINE_WIDTH; 16 | 17 | 18 | function Slimp3(data) { 19 | this.addr = data.addr || "10.0.1.222"; 20 | this.port = data.port || Slimp3.COM_PORT; 21 | this.devices = {}; 22 | this.debug = data.debug; 23 | this.connect(); 24 | }; 25 | util.inherits(Slimp3, EventEmitter); 26 | 27 | Slimp3.REMOTE_CODES = { 28 | 0x00FF: "VolumeDown", 29 | 0x807F: "VolumeUp", 30 | 0x10EF: "Play", 31 | 0x20DF: "Pause", 32 | 0xD02F: "Right", 33 | 0x906f: "Left", 34 | 0xe01f: "Up", 35 | 0xb04f: "Down", 36 | 0xa05f: "NextTrack", 37 | 0xc03f: "PrevTrack", 38 | 39 | } 40 | 41 | Slimp3.prototype.exec = function(command, data) { 42 | if (command == "ShowTrackInfo") { 43 | console.log("show: ", data); 44 | 45 | 46 | this.sendText(data.name + " - " + data.artist + " - " + data.album ); 47 | 48 | // var scene = data.scene; 49 | // var unit = data.zone || data.controlUnit; 50 | // unit = this.controlUnits[unit]; 51 | // this.selectScene([unit], scene) 52 | } 53 | }; 54 | 55 | Slimp3.prototype.sendCommand = function(buf) { 56 | var command = new Buffer({0:"l".charCodeAt(0), length:18}); 57 | command = Buffer.concat([command, buf]); 58 | if (this.debug) console.log("SLIMP3 Sending:", this.addr + ":" + this.port + "\n\t", command) 59 | this.server.send(command, 0, command.length, this.port, this.addr); 60 | }; 61 | 62 | Slimp3.prototype.sendText = function(string) { 63 | var message = new Buffer(NUM_CHARS * 2); 64 | 65 | for (var i = 0; i < string.length; i++) { 66 | message[i * 2] = CODE_DATA; 67 | message[i * 2 + 1] = string.charCodeAt(i); 68 | }; 69 | 70 | for (; i < NUM_CHARS; i++) { // Fill out the rest of the text view 71 | message[i * 2] = CODE_DATA; 72 | message[i * 2 + 1] = 0x20; 73 | }; 74 | 75 | this.sendCommand(message); 76 | } 77 | 78 | 79 | 80 | Slimp3.prototype.parseData = function(data) { 81 | 82 | } 83 | 84 | // Connection 85 | 86 | Slimp3.COM_PORT = 3483; 87 | 88 | Slimp3.prototype.connect = function() { 89 | this.server = dgram.createSocket("udp4"); 90 | this.server.on("message", this.handleMessage.bind(this)); 91 | this.server.on("listening", function () { 92 | var address = this.server.address(); 93 | if (this.debug) console.log("server listening " + address.address + ":" + address.port); 94 | this.sendText("Server reconnected"); 95 | }.bind(this)); 96 | this.server.bind(Slimp3.COM_PORT); 97 | // this.server.setEncoding(); 98 | // this.server.on('data', this.handleData.bind(this)); 99 | // this.server.on('error', this.handleError.bind(this)); 100 | }; 101 | 102 | Slimp3.prototype.handleIR = function(code) { 103 | 104 | // if (this.debug) console.log("Got IR: 0x", code.toString(16)); 105 | var command = Slimp3.REMOTE_CODES[code] || code.toString(16); 106 | this.emit("DeviceEvent", command); 107 | 108 | } 109 | 110 | Slimp3.prototype.handleMessage = function(msg, rinfo) { 111 | if (this.debug) console.log("SlimServer got: '" + msg + "' from " + 112 | rinfo.address + ":" + rinfo.port, msg[0], msg.toString('utf8', 0, 1)); 113 | 114 | switch (msg.toString('utf8', 0, 1)) { 115 | case "d": 116 | var message = new Buffer("D"); 117 | this.server.send(message, 0, message.length, rinfo.port, rinfo.address); 118 | this.addr = rinfo.address; 119 | this.port = rinfo.port; 120 | this.sendText("Server connected"); 121 | this.emit("DeviceEvent", "Connected"); 122 | 123 | break; 124 | case "h": 125 | // sendText("Got Hello", true); 126 | break; 127 | case "i": 128 | var code = msg.readUInt16BE(10); 129 | this.handleIR(code); 130 | break; 131 | // Field Value/Description 132 | // 0 'i' as in "IR" 133 | // 1 0x002..5 player's time since startup in ticks @625 KHz 134 | // 6 0xFF (will eventually be an identifier for different IR code sets) 135 | // 7 number of meaningful bits - always 16 (0x10) for JVC 136 | // 8..11 the 32-bit IR code 137 | // 12..17 MAC address 138 | 139 | 140 | 141 | } 142 | 143 | 144 | }; 145 | 146 | 147 | 148 | module.exports = Slimp3; -------------------------------------------------------------------------------- /modules/powermate/powermate.js: -------------------------------------------------------------------------------- 1 | // Very simple remote powermate processor 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | var url = require('url'); 6 | var io = require('socket.io'); 7 | 8 | function PowerMate(data) { 9 | this.port = data.port || 9003 10 | this.devices = data.devices; 11 | this.knobs = {}; 12 | this.socket = require('socket.io')(this.port) 13 | this.socket.on('connection', this.handleSocketConnection.bind(this)); 14 | this.socket.on('error', this.handleSocketError.bind(this)); 15 | this.sendRawEvents = data.sendRawEvents; 16 | }; 17 | util.inherits(PowerMate, EventEmitter); 18 | 19 | PowerMate.prototype.getKnob = function(id) { 20 | var knob = this.knobs[id]; 21 | if (!knob) { 22 | knob = new PowerMateKnob(id); 23 | knob.context = this.devices[id]; 24 | knob.on("DeviceEvent", this.emit.bind(this, "DeviceEvent")); // reemit events 25 | this.knobs[id] = knob; 26 | } 27 | return knob; 28 | } 29 | 30 | PowerMate.prototype.handleSocketConnection = function(socket) { 31 | socket.on('error', this.handleSocketError.bind(this)); 32 | socket.on('disconnect', this.handleSocketClose.bind(this, socket)); 33 | socket.on('hostname', function(hostname) { 34 | console.log("hostname", hostname); 35 | socket.hostname = hostname; 36 | }.bind(this)); 37 | socket.on('knob-up', function() { 38 | this.getKnob(socket.hostname).knobUp(); 39 | if (this.debug) console.log("knob-up"); 40 | }.bind(this)); 41 | socket.on('knob-down', function() { 42 | this.getKnob(socket.hostname).knobDown(); 43 | if (this.debug) console.log("knob-down"); 44 | }.bind(this)); 45 | socket.on('knob-turn', function(delta) { 46 | this.getKnob(socket.hostname).knobTurned(delta); 47 | if (this.debug) console.log("knob-turn", delta); 48 | }.bind(this)); 49 | }; 50 | 51 | PowerMate.prototype.handleSocketError = function(socket) { 52 | console.log("! PM Web socket error", socket); 53 | }; 54 | 55 | PowerMate.prototype.handleSocketClose = function(socket) {}; 56 | 57 | 58 | // Knob functions 59 | function PowerMateKnob(id) { 60 | this.id = id; 61 | } 62 | util.inherits(PowerMateKnob, EventEmitter); 63 | 64 | PowerMateKnob.DOUBLE_PRESS_DELAY = 200; 65 | 66 | 67 | PowerMateKnob.prototype.knobTurned = function(delta) { 68 | 69 | if (this.pressed) { 70 | this.totalDelta += parseInt(delta); 71 | this.totalDistance += Math.abs(delta); 72 | } 73 | 74 | if (this.totalDistance > 2) { // Ignore a small amount of turning 75 | this.turned = true; 76 | clearTimeout(this.holdTimeout); 77 | } 78 | 79 | if (!this.flicked && Math.abs(this.totalDelta) > 10) { 80 | this.emit("DeviceEvent", this.pressed ? "KnobPressFlicked" : "KnobFlicked" , {context: this.context, value: delta, totalDelta: this.totalDelta}); 81 | this.flicked = true; 82 | } 83 | 84 | console.log(this.id, delta); 85 | this.emit("DeviceEvent", this.pressed ? "KnobPressTurned" : "KnobTurned" , {context: this.context, value: delta, totalDelta: this.totalDelta}); 86 | } 87 | 88 | PowerMateKnob.prototype.knobDown = function() { 89 | if (this.sendRawEvents) this.emit("DeviceEvent", "KnobDown", {context: this.context}); 90 | 91 | this.pressed = true; 92 | this.turned = false; 93 | this.held = false; 94 | this.flicked = false 95 | this.totalDelta = 0; 96 | this.totalDistance = 0; 97 | 98 | if (this.doubleTimeout) { 99 | clearTimeout(this.doubleTimeout); 100 | delete this.doubleTimeout; 101 | this.knobDoublePressed(); 102 | this.held = true; 103 | } else { // could do double-tap-hold, but that is a bit much. 104 | this.holdTimeout = setTimeout(this.knobLongPressed.bind(this), 600); 105 | } 106 | } 107 | 108 | PowerMateKnob.prototype.knobUp = function() { 109 | if (this.sendRawEvents) this.emit("DeviceEvent", "KnobUp", {context: this.context}); 110 | this.totalDelta = 0; 111 | this.totalDistance = 0; 112 | this.pressed = false; 113 | clearTimeout(this.holdTimeout); 114 | 115 | if (!this.turned && !this.held) { 116 | // send normal press after a delay 117 | this.doubleTimeout = setTimeout(this.knobPressed.bind(this), PowerMateKnob.DOUBLE_PRESS_DELAY); 118 | }; 119 | } 120 | 121 | PowerMateKnob.prototype.knobPressed = function() { 122 | this.emit("DeviceEvent", "KnobPressed", {context: this.context}); 123 | delete this.doubleTimeout; 124 | } 125 | 126 | PowerMateKnob.prototype.knobDoublePressed = function() { 127 | this.emit("DeviceEvent", "KnobDoublePressed", {context: this.context}); 128 | } 129 | 130 | PowerMateKnob.prototype.knobLongPressed = function() { 131 | this.held = true; 132 | this.emit("DeviceEvent", "KnobLongPressed", {context: this.context}); 133 | } 134 | 135 | module.exports = PowerMate; -------------------------------------------------------------------------------- /modules/syslog/lib/syslog-messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * syslog-messages.js 3 | * 4 | * Encoding and decoding of CentOS syslog messages. 5 | * 6 | * @version: 0.1.0 7 | * @author Frank Grimm (http://frankgrimm.net) 8 | * 9 | */ 10 | 11 | // returns textual representation for a severity level (0-9), otherwise "N/A" 12 | exports.getSeverityDescription = function (severity) { 13 | switch(severity) { 14 | case 0: return "Emergency"; 15 | case 1: return "Alert"; 16 | case 2: return "Critical"; 17 | case 3: return "Error"; 18 | case 4: return "Warning"; 19 | case 5: return "Notice"; 20 | case 6: return "Informational"; 21 | case 7: return "Debug"; 22 | default: return "N/A"; 23 | } 24 | } 25 | 26 | // returns facility names according to RFC3164 27 | exports.facilityIdToName = function (id) { 28 | switch(id) { 29 | case 0: return "kernel messages"; 30 | case 1: return "user-level messages"; 31 | case 2: return "mail system"; 32 | case 3: return "system daemons"; 33 | case 4: return "security/authorization messages"; 34 | case 5: return "messages generated internally by syslogd"; 35 | case 6: return "line printer subsystem"; 36 | case 7: return "network news subsystem"; 37 | case 8: return "UUCP subsystem"; 38 | case 9: return "clock daemon"; 39 | case 10: return "security/authorization messages"; 40 | case 11: return "FTP daemon"; 41 | case 12: return "NTP subsystem"; 42 | case 13: return "log audit"; 43 | case 14: return "log alert"; 44 | case 15: return "clock daemon"; 45 | case 16: return "local use 0"; 46 | case 17: return "local use 1"; 47 | case 18: return "local use 2"; 48 | case 19: return "local use 3"; 49 | case 20: return "local use 4"; 50 | case 21: return "local use 5"; 51 | case 22: return "local use 6"; 52 | case "23": return "local use 7"; 53 | default: 54 | return "UNKNOWN"; 55 | } 56 | } 57 | 58 | exports.encodeMessage = function (msgobject, callback) { 59 | // only act on valid facility & severity 60 | if (msgobject.facility_id == -1 || msgobject.severity_id == -1) 61 | callback(null); 62 | 63 | // recalculate pri part to make sure it matches facility and severity 64 | msgobject.pri = msgobject.facility_id * 8 + msgobject.severity_id; 65 | 66 | var msgstring = "<" + msgobject.pri + ">" + msgobject.tag; 67 | if (msgobject.pid != "") { 68 | msgstring += "[" + msgobject.pid + "]"; 69 | } 70 | msgstring += ": " + msgobject.content + "\n"; 71 | 72 | callback(msgstring); 73 | } 74 | 75 | exports.decodeMessage = function (msg, callback) { 76 | var result = {"original": msg, 77 | "received": new Date(), 78 | "pri": -1, 79 | "facility_id": -1, 80 | "facility": "UNKNOWN", 81 | "severity_id": -1, 82 | "severity": "N/A", 83 | "tag": "", 84 | "pid": "", 85 | "content": ""}; 86 | 87 | // preserve original message 88 | result.original = msg; 89 | 90 | var vcharExp = new RegExp("[a-zA-Z0-9 \t\/]"); 91 | var partStart = -1, partEnd = -1; 92 | partStart = msg.indexOf('<'); 93 | 94 | if (partStart > -1) { 95 | partEnd = msg.indexOf('>', partEnd); 96 | if (partEnd > -1) { 97 | result.pri = parseInt(msg.substring(partStart+1, partEnd)); 98 | 99 | // decode pri part into facility# and severity 100 | result.severity_id = result.pri % 8; 101 | result.facility_id = (result.pri - result.severity_id) / 8; 102 | 103 | // lookup facility identifier 104 | result.facility = exports.facilityIdToName(result.facility_id); 105 | // lookup textual description for severity 106 | result.severity = exports.getSeverityDescription(result.severity_id); 107 | 108 | // seperate content, tag and optionally pid 109 | msg = msg.substr(partEnd + 1); 110 | for(var i = 0; i < msg.length; i++) { 111 | if (!vcharExp.test(msg.substr(i, 1))) { 112 | result.tag = msg.substring(0, i); 113 | msg = msg.substr(i); 114 | break; 115 | } 116 | } 117 | 118 | if (msg.substr(0, 1) == "[") { 119 | partEnd = msg.indexOf("]"); 120 | if (partEnd > -1) { 121 | result.pid = msg.substring(1, partEnd); 122 | result.content = msg.substr(partEnd + 1); 123 | } else { 124 | result.content = msg; 125 | } 126 | } else { 127 | result.content = msg; 128 | } 129 | 130 | // remove ":", ": " at the start and "\n" at the end 131 | if (result.content.substr(0, 1) == ":") { 132 | result.content = result.content.substr(1); 133 | if (result.content.substr(0, 1) == " ") 134 | result.content = result.content.substr(1); 135 | } 136 | if (result.content.substr(result.content.length - 1, 1) == "\n") 137 | result.content = result.content.substr(0, result.content.length - 1); 138 | } 139 | } 140 | 141 | // invoke the callback 142 | callback(result); 143 | } 144 | -------------------------------------------------------------------------------- /examples/emerson/emerson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is what Glen uses to run his house 3 | */ 4 | 5 | var PATH_TO_ROUTE = "../../"; 6 | var PATH_TO_MODULES = "../../modules/"; 7 | var Insteon = require(PATH_TO_MODULES + 'insteon').Insteon; 8 | var Sonos = require(PATH_TO_MODULES + 'sonos').Sonos; 9 | var RedEye = require(PATH_TO_MODULES + 'redeye').RedEye; 10 | var Web = require(PATH_TO_MODULES + 'web').Web; 11 | var Route = require(PATH_TO_ROUTE).Route; 12 | 13 | // Map of commands to routers that service that command. 14 | var route = new Route(); 15 | 16 | var insteon = route.addDevice({ 17 | type : Insteon, 18 | name : "Insteon", 19 | init : { 20 | host : "10.0.1.120", 21 | commands : { 22 | }, 23 | devices : { 24 | "StudyLamp" : "1AD883", 25 | "StudyLights" : "01", 26 | "BedRoomLights" : "1FC81E", 27 | "LivingRoomLights" : "21", 28 | "DiningRemote" : "1C483B", 29 | "FrontDoorRemote" : "1C4C33", 30 | "StudyRemote" : "1C4943", 31 | "BedRoomRemote" : "1C5418", 32 | "MotionLivingRoom" : "14DEDD" 33 | } 34 | } 35 | }); 36 | 37 | var sonos = route.addDevice({ 38 | type : Sonos, 39 | name : "Sonos", 40 | init : { 41 | host : "10.0.1.16", 42 | } 43 | }); 44 | 45 | var redeye = route.addDevice({ 46 | type : RedEye, 47 | name : "RedEye", 48 | init : { 49 | host : "10.0.1.19", 50 | commands : { 51 | "ProjectorPower" : "/devicedata/1189-99999-02.isi", 52 | "ProjectorCancel" : "/devicedata/1189-99999-10.isi", 53 | "ProjectorOK" : "/devicedata/1189-99999-05.isi", 54 | "InputSonos" : "/devicedata/CaptubELVo4", 55 | "InputPC" : "/devicedata/1377-99999-05.isi" 56 | } 57 | } 58 | }); 59 | 60 | var web = route.addDevice({ 61 | type : Web, 62 | name : "Web", 63 | init : { 64 | port : 8000, 65 | dir : __dirname + "/web/" 66 | } 67 | }); 68 | 69 | // Simple map of events to commands. 70 | route.addEventMap({ 71 | // Front door 72 | "Insteon.FrontDoorRemote.Off.2" : ["Sonos.Pause"], 73 | "Insteon.FrontDoorRemote.Off.4" : [ 74 | "Sonos.Pause", 75 | "Insteon.LivingRoomLights.Off", 76 | "Insteon.BedRoomLights.Off", 77 | "Insteon.StudyLights.Off", 78 | ], 79 | 80 | // Bedroom 81 | "Insteon.BedRoomRemote.On" : "Insteon.BedRoomLights.On", 82 | "Insteon.BedRoomRemote.Off" : "Insteon.BedRoomLights.Off", 83 | 84 | // Dining room 85 | "Insteon.DiningRemote.On.2" : [ 86 | "Sonos.Play", 87 | "RedEye.InputSonos" 88 | ], 89 | "Insteon.DiningRemote.Off.2" : ["Sonos.Pause"], 90 | 91 | // Study 92 | "Insteon.StudyRemote.On" : "Insteon.StudyLights.On", 93 | "Insteon.StudyRemote.Off" : "Insteon.StudyLights.Off", 94 | "Insteon.StudyRemote.On.2" : "Insteon.StudyLamp.On", 95 | "Insteon.StudyRemote.Off.2" : "Insteon.StudyLamp.Off", 96 | 97 | "Sonos.Started" : ["RedEye.InputSonos"], 98 | "Web.PlayPause" : ["Sonos.PlayPause"], 99 | "Web.Play" : ["Sonos.Play"], 100 | "Web.Pause" : ["Sonos.Pause"], 101 | "Web.LivingRoomLightsOn" : ["Insteon.LivingRoomLights.On"], 102 | "Web.LivingRoomLightsOff" : ["Insteon.LivingRoomLights.Off"], 103 | "Web.BedRoomLightsOn" : ["Insteon.BedRoomLights.On"], 104 | "Web.BedRoomLightsOff" : ["Insteon.BedRoomLights.Off"], 105 | "Web.StudyLightsOn" : ["Insteon.StudyLights.On"], 106 | "Web.StudyLightsOff" : ["Insteon.StudyLights.Off"], 107 | "Web.StudyLampOn" : ["Insteon.StudyLamp.On"], 108 | "Web.StudyLampOff" : ["Insteon.StudyLamp.Off"], 109 | "Web.MediaPCStarted" : [ 110 | "Insteon.LivingRoomLights.Off", 111 | "Sonos.Pause", 112 | [ 113 | "RedEye.ProjectorPower", 114 | "Wait.500", 115 | "RedEye.ProjectorCancel", 116 | "RedEye.InputPC" 117 | ] 118 | ], 119 | "Web.MediaPCEnded" : [ 120 | "Insteon.LivingRoomLights.On", 121 | [ 122 | "RedEye.ProjectorPower", 123 | "Wait.500", 124 | "RedEye.ProjectorOK", 125 | "RedEye.InputSonos" 126 | ] 127 | ], 128 | }); 129 | 130 | // Programmatically added listeners 131 | route.map("Insteon.FrontDoorRemote.On.2", function() { 132 | sonos.exec("Play"); 133 | redeye.exec("InputSonos"); 134 | }); 135 | 136 | /* 137 | // Motion detection. 138 | var state = { 139 | "LivingRoomMotion" : { 140 | started : 0, 141 | ended : 0, 142 | }, 143 | "GlenHome" : { 144 | arrived : 0 145 | } 146 | } 147 | 148 | route.map("Web.GlenHome", function() { 149 | state.GlenHome = new Date(); 150 | }) 151 | 152 | route.map("Insteon.025014DEDD000001C71101", function() { 153 | var date = new Date(); 154 | // If it's the afternoon and the livingroom hasn't seen motion in over four hours 155 | if (date.getHours > 16 && date.getHours < 20 && (date - motion.LivingRoom.ended > 1000 * 60 * 60 * 5)) { 156 | insteon.exec("LivingRoomLightsOn"); 157 | sonos.exec("Play"); 158 | } 159 | motion.LivingRoom.started = new Date(); 160 | }); 161 | 162 | route.map("Insteon.025014DEDD000001C71301", function() { 163 | motion.LivingRoom.ended = new Date(); 164 | }); 165 | */ 166 | -------------------------------------------------------------------------------- /modules/ifttt/ifttt.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var http = require("http"); 4 | var url = require("url"); 5 | var Deserializer = require("xmlrpc/lib/deserializer"); 6 | var Serializer = require("xmlrpc/lib/serializer"); 7 | var querystring = require("querystring"); 8 | 9 | // Provides a webhook to the WordPress module of IFTTT. Title is emitted as an event 10 | // All other fields are passed as parameters. 11 | // The listening port must be mapped to port 80 on the external ip address 12 | // Use the blog URL http://IP_ADDR/xmlrpc.php 13 | 14 | 15 | function IFTTT(data) { 16 | this.port = data.port || 8080; 17 | this.debug = data.debug; 18 | this.key = data.key; 19 | this.server = http.createServer(this.handleRequest.bind(this)); 20 | this.server.once("listening", function () { 21 | if (this.debug) ("* IFTTT: listening on port", this.server.address().port); 22 | }.bind(this)); 23 | this.server.listen(this.port); 24 | } 25 | util.inherits(IFTTT, EventEmitter); 26 | 27 | 28 | // curl -X POST -H "Content-Type: application/json" -d '{"value1":"hi","value2":"there","value3":"you"}' https://maker.ifttt.com/trigger/pony/with/key/b6O5Jey3VGLt7c-0z8UIw3 29 | // IFTTT.prototype.exec = function(command, params) { 30 | // console.log("* IFTTT Executing: [" + command + "] : " + JSON.stringify(params)); 31 | // var baseURL = "https://maker.ifttt.com/trigger/" + command + "/with/key/" + this.key; 32 | // var opt = url.parse(options.url); 33 | // opt.headers = {}; 34 | // opt.method = "GET"; 35 | // opt.contentType = "application/json"; 36 | // opt.data = JSON.stringify(params), 37 | // opt.success = function(data, res) { 38 | // that.success(success, JSON.parse(data), res); 39 | // }; 40 | // opt.error = function(data, err, res) { 41 | // that.error(error, "error adding a playlist", data, err, res); 42 | // }; 43 | 44 | // var req = https.request(opt, function(res) { 45 | // res.setEncoding('utf8'); 46 | // var body = ""; 47 | // res.on('data', function(chunk) { 48 | // body += chunk; 49 | // }); 50 | // res.on('end', function() { 51 | // if(res.statusCode === 200) { 52 | // opt.success(body, res); 53 | // } else { 54 | // opt.error(body, null, res); 55 | // } 56 | // }); 57 | // res.on('error', function() { 58 | // opt.error(null, Array.prototype.slice.apply(arguments), res); 59 | // }); 60 | // }); 61 | // if(typeof opt.data !== "undefined") req.write(options.data); 62 | // req.end(); 63 | // } 64 | 65 | 66 | IFTTT.prototype.handleRequest = function (request, response) { 67 | var deserializer = new Deserializer(); 68 | deserializer.deserializeMethodCall(request, function(error, methodName, params) { 69 | var xml = null; 70 | if (!error) { 71 | if (this.debug) ("* IFTTT deserialized: %s(%s)", methodName, JSON.stringify(params)); 72 | var statusCode = 200, xml = null; 73 | switch (methodName) { 74 | case 'mt.supportedMethods': // codex.wordpress.org/XML-RPC_MovableType_API#mt.supportedMethods 75 | // this is used by IFTTT to verify the site is actually a wordpress blog ;-) 76 | xml = Serializer.serializeMethodResponse(['metaWeblog.getRecentPosts', 'metaWeblog.newPost']); 77 | break; 78 | case 'metaWeblog.getRecentPosts': // codex.wordpress.org/XML-RPC_MetaWeblog_API#metaWeblog.getRecentPosts 79 | // this is the authentication request from IFTTT 80 | // send a blank blog response 81 | // this also makes sure that the channel is never triggered 82 | xml = Serializer.serializeMethodResponse([]); 83 | break; 84 | case 'metaWeblog.newPost': // codex.wordpress.org/XML-RPC_WordPress_API/Posts#wp.newPost 85 | // This is the actual webhook. Parameters are provided via fields in IFTTT's GUI. By convention 86 | // we put the target URL in the Tags field (passed as mt_keywords). e.g. params 87 | // [0, "user", "password", {"title":"...","description":"...","categories":[...],mt_keywords":[webhook url]}] 88 | params.shift(); // blogid? 89 | var username = params.shift(); 90 | var password = params.shift(); 91 | var params = params.shift(); 92 | params.username = username; 93 | params.password = password; 94 | 95 | this.emit("DeviceEvent", params.title, params); 96 | xml = Serializer.serializeMethodResponse(Date.now().toString(32)); // a "postid", presumably ignored by IFTTT 97 | break; 98 | default: 99 | error = { faultCode: -32601, faultString: "server error. requested method not found" }; 100 | break; 101 | } 102 | } 103 | if (error) { 104 | xml = Serializer.serializeFault(error); 105 | } 106 | if (xml && statusCode) response.writeHead(statusCode, {'Content-Type': 'text/xml', 'Content-Length': xml.length}); 107 | response.end(xml); 108 | }.bind(this)); 109 | } 110 | 111 | 112 | module.exports = IFTTT; 113 | -------------------------------------------------------------------------------- /modules/insteon/insteon.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | 5 | /** 6 | * 7 | */ 8 | function Insteon(data) { 9 | this.host = data.host; 10 | this.connect(); 11 | 12 | // Dupe preventer. Stores IDs. 13 | this.history = {}; 14 | 15 | // Custom commands (name : insteon command) 16 | this.commands = data.commands ? data.commands : {}; 17 | 18 | // Map of device names to ids. 19 | this.devices = data.devices ? data.devices : {}; 20 | 21 | // Create reverse devices map. 22 | this.device_ids = {}; 23 | for (var name in this.devices) { 24 | var id = this.devices[name]; 25 | this.device_ids[id] = name; 26 | 27 | // Try to infer the command type by the size of the id 28 | // two digits is a group, six is a device. 29 | // 30 | // TODO: Do this on the fly, so that we don't have to stuff 31 | // the commands index. 32 | if (id.length == 6) { 33 | this.commands[name + ".On"] = "0262" + id + "0F11FF"; 34 | this.commands[name + ".Off"] = "0262" + id + "0F13FF"; 35 | } else if (id.length == 2) { 36 | this.commands[name + ".On"] = "0261" + id + "11" + id; 37 | this.commands[name + ".Off"] = "0261" + id + "13" + id; 38 | } 39 | } 40 | } 41 | util.inherits(Insteon, EventEmitter); 42 | 43 | Insteon.SMARTLINC_PLM_PORT = 9761; 44 | 45 | Insteon.prototype.exec = function(command, data) { 46 | if (!(command in this.commands)) return; 47 | console.log("* Insteon Executing: " + command); 48 | var path = this.commands[command]; 49 | this.ping(path); 50 | }; 51 | 52 | Insteon.prototype.ping = function(path) { 53 | path = new Buffer(path, "hex"); 54 | this.client.write(path); 55 | }; 56 | 57 | Insteon.prototype.connect = function() { 58 | this.reconnecting_ = false; 59 | this.client = net.connect({ 60 | host : this.host, 61 | port : Insteon.SMARTLINC_PLM_PORT 62 | }, this.handleConnected.bind(this)); 63 | this.client.on('data', this.handleData.bind(this)); 64 | this.client.on('error', this.handleError.bind(this)); 65 | }; 66 | 67 | Insteon.prototype.reconnect = function() { 68 | if (this.reconnecting_) return; 69 | 70 | this.reconnecting_ = true; 71 | setTimeout(this.connect.bind(this), 1000); 72 | } 73 | 74 | Insteon.prototype.handleConnected = function() { 75 | this.emit("DeviceEvent", "Insteon.Connected"); 76 | }; 77 | 78 | Insteon.prototype.lookupCommandName = function(command) { 79 | switch (command) { 80 | case "11": return "On"; 81 | case "13": return "Off"; 82 | case "17": return "HoldStart"; 83 | case "18": return "HoldStop"; 84 | default : return command; 85 | } 86 | } 87 | 88 | Insteon.prototype.parse0250 = function(data) { 89 | var parsed = data.match(/(....)(......)(......)(..)(..)(..)/); 90 | if (parsed.length != 7) return data; 91 | var device = parsed[2]; 92 | var target = parsed[3]; 93 | 94 | // This is actually super annoying - C7-based commands arrive first 95 | // and quickly, but don't always arrive. The other commands often take 96 | // seconds, but eventually arrive. We want to key things off the C7, 97 | // but use the others without duplicates. 98 | var flags = parsed[4]; 99 | var command = parsed[5]; 100 | var alt = parsed[6]; 101 | 102 | var out = { 103 | name : (device in this.device_ids) ? this.device_ids[device] : device, 104 | command : this.lookupCommandName(command), 105 | control : (flags == "C7") ? parseInt(target) : parseInt(alt) 106 | } 107 | return out; 108 | } 109 | 110 | Insteon.prototype.handleData = function(data) { 111 | data = new Buffer(data).toString("hex").toUpperCase(); 112 | 113 | // Try to decode the string. 114 | var cmd = data.substr(0, 4); 115 | switch (cmd) { 116 | case '0250': 117 | this.emitDeviceStatus(this.parse0250(data)); 118 | break; 119 | default: 120 | this.emit("DeviceEvent", data); 121 | break; 122 | } 123 | }; 124 | 125 | /** 126 | * Insteon sends two types of message in response to status events 127 | * one of them (C7) is very quick but unreliable, the others are 128 | * slow but more reliable. As the controllers send both, we want 129 | * to use the first if we can, but use the second if it isn't sent 130 | * and we need to do so without dupes. 131 | */ 132 | Insteon.prototype.emitDeviceStatus = function(data) { 133 | var date = new Date(); 134 | var control_name = data.name + "." + data.control; 135 | 136 | if ((control_name in this.history) && 137 | this.history[control_name].command == data.command && 138 | date - this.history[control_name].time < 3000) { 139 | return; 140 | } 141 | 142 | this.history[control_name] = { 143 | command : data.command, 144 | time : date 145 | }; 146 | 147 | var out = [ 148 | data.name, 149 | data.command 150 | ]; 151 | if (data.control > 1) 152 | out.push(data.control); 153 | this.emit("DeviceEvent", out.join(".")); 154 | } 155 | 156 | Insteon.prototype.handleError = function(e) { 157 | this.emit("DeviceEvent", "Insteon.Error"); 158 | console.log(e); 159 | this.reconnect(); 160 | }; 161 | 162 | Insteon.prototype.handleEnd = function() { 163 | this.emit("DeviceEvent", "Insteon.Disconnected"); 164 | this.reconnect(); 165 | }; 166 | 167 | exports.Insteon = Insteon; 168 | -------------------------------------------------------------------------------- /modules/web/web_experimental.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var https = require('https'); 4 | var url = require('url'); 5 | var static = require('node-static'); 6 | var util = require('util'); 7 | var io = require('socket.io'); 8 | var path = require('path'); 9 | var fs = require('fs'); 10 | 11 | function Web(data) { 12 | this.basedir = data.basedir; 13 | 14 | if (data.securePort) { 15 | var options = { 16 | key: data.key, 17 | cert: data.cert, 18 | ca: data.ca 19 | } 20 | 21 | this.password = data.password; 22 | this.secureServer = https.createServer(options, this.handleReq.bind(this)).listen(data.securePort || 8081); 23 | this.secureSocket = io.listen(this.secureServer, { log: false }); 24 | this.secureSocket.on('connection', this.handleSocketConnection.bind(this)); 25 | this.secureSocket.on('error', this.handleSocketError.bind(this)); 26 | } 27 | this.server = http.createServer(this.handleReq.bind(this)).listen(data.port || 8080); 28 | if (data.eventPort) this.eventServer = http.createServer(this.handleEventReq.bind(this)).listen(data.eventPort); 29 | this.socket = io.listen(this.server, { log: false }); 30 | this.socket.on('connection', this.handleSocketConnection.bind(this)); 31 | this.socket.on('error', this.handleSocketError.bind(this)); 32 | 33 | this.handlers = data.handlers; 34 | this.staticServer = new static.Server(data.dir); 35 | this.clients = []; 36 | this.textResponse = null; 37 | }; 38 | util.inherits(Web, EventEmitter); 39 | 40 | Web.prototype.setTextResponse = function (response) { 41 | this.textResponse = response; 42 | } 43 | 44 | // This function only allows event requests (for external use) 45 | Web.prototype.handleEventReq = function(req, res) { 46 | var info = url.parse(req.url, true); 47 | var prefix = "/event/"; 48 | if (info.pathname.indexOf(prefix) == 0) { 49 | this.handleReq(req,res); 50 | } else { 51 | res.end(); 52 | } 53 | } 54 | 55 | Web.prototype.handleReq = function(req, res) { 56 | var info = url.parse(req.url, true); 57 | 58 | var matchingHandler = this.handlers ? this.handlers[info.pathname] : null; 59 | // Emit requests to /event/pie?params as 'Web.pie?params' 60 | var prefix = "/event/"; 61 | if (info.pathname.indexOf(prefix) == 0) { 62 | info.pathname = info.pathname.substring(prefix.length); 63 | this.textResponse = null; 64 | this.handleEvent(info); 65 | res.writeHead(200, {'Content-Type': 'text/plain'}); 66 | if (this.textResponse) res.write(this.textResponse); 67 | res.end(''); 68 | } else if (info.pathname.indexOf('/state') == 0) { 69 | var json = JSON.stringify(this.state.allValues()); 70 | res.writeHead(200, { 'Content-Type': 'application/json' }); 71 | res.write(JSON.stringify(json)); 72 | res.end(); 73 | } else if (info.pathname == "/" && this.basedir) { 74 | res.writeHead(302, {'Location': this.basedir}); 75 | res.end(); 76 | } else if (matchingHandler) { 77 | matchingHandler(req, res); 78 | } else { 79 | if (this.debug) console.log(req.url); 80 | this.staticServer.serve(req, res, function (err, result) { 81 | if (err) { // There was an error serving the file 82 | util.error("Error serving " + req.url + " - " + err.message); 83 | 84 | // Respond to the client 85 | res.writeHead(err.status, err.headers); 86 | res.end(); 87 | } 88 | }); 89 | } 90 | }; 91 | 92 | Web.prototype.handleEvent = function(info) { 93 | this.emit("DeviceEvent", info.pathname, info.query); 94 | } 95 | 96 | Web.prototype.handleSocketConnection = function(socket) { 97 | // this.emit("DeviceEvent", "ClientConnected"); 98 | this.clients.push(socket); 99 | try { 100 | socket.emit('state', this.state.allValues()); 101 | } catch (e) { 102 | console.log("! Web emit error:", e, this.state.allValues()); 103 | } 104 | socket.on('message', this.handleSocketMessage.bind(this)); 105 | socket.on('error', this.handleSocketError.bind(this)); 106 | socket.on('disconnect', this.handleSocketClose.bind(this, socket)); 107 | }; 108 | 109 | Web.prototype.handleSocketError = function(socket) { 110 | console.log("! Web socket error", socket); 111 | }; 112 | 113 | Web.prototype.handleSocketMessage = function(message) { 114 | this.handleEvent(url.parse(message, true)); 115 | }; 116 | 117 | Web.prototype.handleSocketClose = function(socket) { 118 | //this.emit("DeviceEvent", "ClientDisconnected"); 119 | for (var i=0; i < this.clients.length; i++) { 120 | if (this.clients[i] == socket) { 121 | this.clients.splice(i, 1); 122 | } 123 | } 124 | }; 125 | 126 | // 127 | // State Changes 128 | // 129 | 130 | Web.prototype.setStateObject = function(object) { 131 | this.state = object; 132 | this.state.addListener('StateEvent', this.stateChanged.bind(this)); 133 | } 134 | 135 | Web.prototype.stateChanged = function(newState) { 136 | for (var i=0; i < this.clients.length; i++) { 137 | this.clients[i].emit('state', newState); 138 | } 139 | }; 140 | 141 | Web.prototype.customStateChanged = function(stateName, newState) { 142 | for (var i=0; i < this.clients.length; i++) { 143 | this.clients[i].emit(stateName, newState); 144 | } 145 | }; 146 | 147 | module.exports = Web; 148 | -------------------------------------------------------------------------------- /modules/denon/denon.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | 5 | /* 6 | var n = new (require("./denon.js")).Denon({ 7 | host : "10.0.1.20", 8 | sources : { 9 | "MacMini" : "GAME", 10 | "Sonos" : "DVR" 11 | } 12 | }); 13 | */ 14 | 15 | function Denon(data) { 16 | this.host = data.host; 17 | this.port = data.port || 23; 18 | 19 | // Map of source names to ids. 20 | this.sources = data.sources ? data.sources : {}; 21 | 22 | // Create reverse sources map. 23 | this.sourceIds = {}; 24 | for (var name in this.sources) { 25 | var id = this.sources[name]; 26 | this.sourceIds[id] = name; 27 | } 28 | 29 | this.debug = data.debug; 30 | this.commandQueue = []; 31 | this.connect(); 32 | setTimeout(this.getStatus.bind(this), 2000); 33 | }; 34 | util.inherits(Denon, EventEmitter); 35 | 36 | Denon.prototype.getStatus = function() { 37 | this.send("SI?"); 38 | this.send("SV?"); 39 | this.send("MV?"); 40 | }; 41 | 42 | Denon.prototype.send = function(string) { 43 | var isFirstRequest = (this.commandQueue.length == 0); 44 | this.commandQueue.push(string); 45 | if (isFirstRequest) 46 | process.nextTick(this.sendNextCommand.bind(this)); 47 | }; 48 | 49 | Denon.prototype.sendNextCommand = function() { 50 | if (!this.commandQueue.length) return; 51 | var string = this.commandQueue.shift(); 52 | this.client.write(string + "\r", "UTF8", function () { 53 | setTimeout(this.sendNextCommand.bind(this), 300); 54 | }.bind(this)); 55 | }; 56 | 57 | Denon.prototype.exec = function(command) { 58 | console.log("* Denon Executing: " + command); 59 | 60 | var segments = command.split("."); 61 | var action = segments.shift(); 62 | var fields = segments; 63 | switch (action) { 64 | case "Switch": 65 | var sourceName = fields.shift(); 66 | sourceId = (sourceName in this.sources) ? this.sources[sourceName] : sourceName; 67 | this.send("SI" + sourceId); 68 | this.emit("DeviceEvent", "Switch." + sourceName); 69 | break; 70 | case "Volume": 71 | var vol = parseInt(fields.shift()); 72 | if (!isNaN(vol)) { 73 | this.send("MV" + vol); 74 | this.emit("DeviceEvent", "Volume", {volume : vol}); 75 | } 76 | break; 77 | case "VolumeUp": 78 | this.volumeUp(); 79 | break; 80 | case "VolumeDown": 81 | this.volumeDown(); 82 | break; 83 | default: 84 | break; 85 | } 86 | }; 87 | 88 | Denon.prototype.getVolume = function(percent) { 89 | this.send("MV?"); 90 | } 91 | 92 | Denon.prototype.setVolume = function(percent) { 93 | percent = Math.round(percent * 2) / 2; 94 | var half = Math.mod(percent, 1.0) == 0.5; 95 | percent = String(Math.floor(percent)); 96 | if (percent.length == 1) percent = "0" + percent; 97 | if (half) percent = percent + "5"; 98 | this.send("MV" + percent); 99 | } 100 | 101 | Denon.prototype.volumeUp = function(data) { 102 | this.send("MV" + "UP"); 103 | } 104 | Denon.prototype.volumeDown = function(data) { 105 | this.send("MV" + "DOWN"); 106 | } 107 | 108 | Denon.prototype.parseData = function(data) { 109 | var event = data.substring(0,2); 110 | var parameter = data.substring(2); 111 | switch (event) { 112 | case "MV": 113 | var volume = parseFloat(parameter); 114 | if (volume) { 115 | if (parameter.length > 2) volume /= 10; 116 | this.volume = volume; 117 | this.emit("StateEvent", {"Denon.Volume" : volume}); 118 | } 119 | break; 120 | case "MU": 121 | var muted = parameter == "ON"; 122 | this.emit("DeviceEvent", muted ? "Muted" : "Unmuted"); 123 | break; 124 | case "SI": 125 | var source = parameter; 126 | this.emit("DeviceEvent", "Source." + source); 127 | this.emit("StateEvent", {"Denon.Source" : volume}); 128 | break; 129 | default: 130 | if (this.debug) console.log("Denon", event, parameter); 131 | } 132 | } 133 | 134 | // Connection 135 | Denon.prototype.connect = function() { 136 | this.reconnecting_ = false; 137 | this.client = net.connect({ 138 | host : this.host, 139 | port : this.port 140 | }, function() { //'connect' listener 141 | this.emit("DeviceEvent", "Connected"); 142 | this.getVolume(); 143 | }.bind(this)); 144 | 145 | this.client.on('data', this.handleData.bind(this)); 146 | this.client.on('error', this.handleError.bind(this)); 147 | this.client.on('close', this.handleError.bind(this)); 148 | }; 149 | 150 | Denon.prototype.reconnect = function() { 151 | if (this.reconnecting_) return; 152 | 153 | this.reconnecting_ = true; 154 | setTimeout(this.connect.bind(this), 1000); 155 | }; 156 | 157 | Denon.prototype.handleData = function(data) { 158 | data = (data + "").trim(); 159 | data = data.split(/\r|\n/); 160 | for (var i = 0; i < data.length; i++) { 161 | this.parseData(data[i]); 162 | } 163 | }; 164 | 165 | Denon.prototype.handleError = function(e) { 166 | if (this.debug) console.log("! Denon\t" + e); 167 | this.reconnect(); 168 | }; 169 | 170 | Denon.prototype.handleEnd = function() { 171 | this.emit("DeviceEvent", "Disconnected"); 172 | this.reconnect(); 173 | }; 174 | 175 | Denon.prototype.log = function(data) { 176 | console.log("Denon LOG:" + data); 177 | this.emit("DeviceEvent", "Logged"); 178 | } 179 | 180 | module.exports = Denon; -------------------------------------------------------------------------------- /examples/cambridge/web/sonos.js: -------------------------------------------------------------------------------- 1 | function Sonos(name, socket, parentNode) { 2 | this.name = name; 3 | this.socket = socket; 4 | this.state = "PAUSED"; 5 | 6 | // Listen for new states 7 | this.socket.on(name + ".TrackInfo", this.handleTrackInfo.bind(this)); 8 | this.socket.on(name + ".PlayingState", this.handlePlayingState.bind(this)); 9 | 10 | // Get state 11 | this.socket.emit("getState", name + ".TrackInfo"); 12 | this.socket.emit("getState", name + ".PlayingState"); 13 | 14 | if (Sonos.CSS_APPENDED == false) { 15 | var css = document.createElement("style"); 16 | css.type = "text/css"; 17 | css.innerHTML = Sonos.CSS 18 | document.body.appendChild(css); 19 | Sonos.CSS_APPENDED = true; 20 | } 21 | 22 | this.node = createElement("div", "sonos", parentNode); 23 | this.nodeBackground = createElement("div", "sonos-background", this.node); 24 | this.nodeArtwork = createElement("div", "sonos-artwork", this.node); 25 | this.nodeName = createElement("div", "sonos-name", this.node, "-"); 26 | this.nodeArtist = createElement("div", "sonos-artist", this.node, "-"); 27 | this.nodeControls = createElement("div", "sonos-controls", this.node); 28 | this.nodeAlbum = createElement("div", "sonos-album", this.nodeControls, "-"); 29 | this.nodePlay = createElement("div", "sonos-play", this.node, ""); // temp move out 30 | 31 | this.node.addEventListener("click", this.handlePlay.bind(this), false); 32 | }; 33 | 34 | Sonos.CSS = " \ 35 | .sonos { \ 36 | position: relative; \ 37 | width: 300px; \ 38 | height: 100px; \ 39 | margin-bottom:1px; \ 40 | font-family: Noto Sans, helvetica, arial, sans-serif; \ 41 | font-size: 12px; \ 42 | padding: 0px !important; \ 43 | color: #888; \ 44 | background-color: #262626; \ 45 | cursor: pointer; \ 46 | box-sizing:border-box; \ 47 | background-clip: padding-box; \ 48 | overflow:hidden; \ 49 | transition: all 0.2s; \ 50 | } \ 51 | .sonos.playing { \ 52 | color:#eee; \ 53 | background-color: #36c; \ 54 | } \ 55 | .sonos:hover { \ 56 | background-color:#3a3a3a; \ 57 | } \ 58 | .sonos-background { \ 59 | position:absolute; \ 60 | top:-10px; \ 61 | left:-10px; \ 62 | width:320px; \ 63 | height:120px; \ 64 | -webkit-filter: grayscale(10%) blur(2px); \ 65 | opacity:1; \ 66 | background-position: center center; \ 67 | background-size: cover; \ 68 | } \ 69 | .sonos-artwork { \ 70 | position:absolute; \ 71 | top:16px; \ 72 | left:16px; \ 73 | border-radius: 32px; \ 74 | width:64px; \ 75 | height:64px; \ 76 | box-sizing:border-box; \ 77 | //border:1px solid white; \ 78 | //box-shadow: 0px 0px 0px 5px rgba(255, 255, 255, 0.2); \ 79 | background-position: center center; \ 80 | background-size: cover; \ 81 | } \ 82 | .sonos-name { \ 83 | position: absolute; \ 84 | top: 30px; \ 85 | left: 94px; \ 86 | font-size:14px; \ 87 | font-weight:bold; \ 88 | width:160px; \ 89 | height:30px; \ 90 | overflow:hidden; \ 91 | text-overflow:ellipsis; \ 92 | white-space:nowrap; \ 93 | } \ 94 | .sonos-artist { \ 95 | position: absolute; \ 96 | top: 50px; \ 97 | left: 94px; \ 98 | width: 160px; \ 99 | overflow:hidden; \ 100 | text-overflow:ellipsis; \ 101 | white-space:nowrap; \ 102 | } \ 103 | .sonos-controls { \ 104 | display:none; \ 105 | position: absolute; \ 106 | bottom: 0px; \ 107 | border-radius: 0px 0px 5px 5px; \ 108 | left: 0px; \ 109 | width: 240px; \ 110 | height: 50px; \ 111 | background-color:#1f579a; \ 112 | border-top:1px solid #6c9fdd; \ 113 | border-bottom:1px solid #124888; \ 114 | box-shadow:0px 0px 8px 1px rgba(0, 0, 0, 0.35); \ 115 | background-image: \ 116 | -webkit-linear-gradient(bottom, rgba(0,0,0,.1) 0%, rgba(255,255,255,.16) 100%), \ 117 | url(noise1.png); \ 118 | } \ 119 | .sonos-album { \ 120 | position: absolute; \ 121 | top: 50px; \ 122 | left: 10px; \ 123 | display:none; \ 124 | } \ 125 | .sonos-play { \ 126 | position: absolute; \ 127 | -top: 5px; \ 128 | -left: 50%; \ 129 | -margin-left:-20px; \ 130 | -width: 39px; \ 131 | -height: 39px; \ 132 | -border-radius: 20px; \ 133 | top:16px; \ 134 | left:16px; \ 135 | border-radius: 32px; \ 136 | width:64px; \ 137 | height:64px; \ 138 | box-sizing:border-box; \ 139 | box-shadow:inset 0px 0px 1px white, 0px 1px 2px 1px rgba(0, 0, 0, 0.6); \ 140 | background-repeat:no-repeat; \ 141 | background-position:center center; \ 142 | background-image:url(sonos-play.png), -webkit-linear-gradient(top, #f5f6f6 0%, #c7d3e1 100%); \ 143 | transition:all 0.2s; \ 144 | opacity:0.2; \ 145 | } \ 146 | .sonos.playing .sonos-play { \ 147 | background-image:url(sonos-pause.png), -webkit-linear-gradient(top, #f5f6f6 0%, #c7d3e1 100%); \ 148 | opacity:1; \ 149 | } \ 150 | .sonos.playing:hover .sonos-play { \ 151 | opacity:1; \ 152 | } \ 153 | "; 154 | Sonos.CSS_APPENDED = false; 155 | 156 | Sonos.prototype.handleTrackInfo = function(details) { 157 | console.log(details); 158 | this.nodeArtist.innerHTML = details.artist; 159 | this.nodeName.innerHTML = details.name; 160 | this.nodeAlbum.innerHTML = details.album; 161 | //this.nodeBackground.style.backgroundImage = ((details.artwork) ? 'url(' + details.artwork + ')' : ""); 162 | //this.nodeArtwork.style.backgroundImage = (details.artwork) ? 'url(' + details.artwork + ')' : ""; 163 | }; 164 | 165 | Sonos.prototype.handlePlayingState = function(details) { 166 | this.state = details.state; 167 | if (this.state == "PLAYING") 168 | this.node.classList.add("playing"); 169 | else 170 | this.node.classList.remove("playing"); 171 | }; 172 | 173 | Sonos.prototype.handlePlay = function() { 174 | if (this.state == "PLAYING") 175 | this.socket.emit("DeviceEvent", this.name + ".Pause"); 176 | else 177 | this.socket.emit("DeviceEvent", this.name + ".Play"); 178 | }; 179 | -------------------------------------------------------------------------------- /modules/ecobee/ecobee.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var url = require('url'); 4 | var http = require('http'); 5 | var https = require('https'); 6 | var http = require('follow-redirects').http 7 | var zlib = require('zlib'); 8 | 9 | function Ecobee(data) { 10 | 11 | }; 12 | util.inherits(Ecobee, EventEmitter); 13 | 14 | // get access token from https://www.ecobee.com/home/developer/api/examples/ex1.shtml 15 | 16 | Ecobee.prototype.exec = function(command, params) { 17 | console.log("* Ecobee Executing: [" + command + "] : " + JSON.stringify(params)); 18 | 19 | if (command == "Stop") { 20 | this.stopCarUpdateLoop(); 21 | } else if (command == "Start") { 22 | this.startCarUpdateLoop(parseInt(params.intervalTime,10)); 23 | } else if (command == "Honk") { 24 | // TODO 25 | } else if (command == "Lock") { 26 | this.sendAction("DOOR_LOCK") 27 | // TODO 28 | } else if (command == "Precondition") { 29 | 30 | } 31 | }; 32 | 33 | Ecobee.prototype.startVehicleCheck = function() { 34 | if (this.isAuthCurrent()) { 35 | if(this.debug) console.log("* Auth Token still valid"); 36 | this.updateStatus(); 37 | } else if (this.refreshToken) { 38 | if(this.debug) console.log("* Logging in with Refresh Token"); 39 | var post_data = "grant_type=refresh_token&refresh_token="+this.refreshToken; 40 | this.login(post_data); 41 | } else { 42 | if(this.debug) console.log("* Logging in with User Credentials"); 43 | var post_data = "grant_type=password" + 44 | "&username="+this.username + 45 | "&password="+this.password + 46 | "&scope=remote_services+vehicle_data"; 47 | this.login(post_data); 48 | } 49 | }; 50 | 51 | Ecobee.prototype.isAuthCurrent = function() { 52 | // Auth tokens expire after 8 hours 53 | var currentTime = new Date(); 54 | currentTime.setMinutes(currentTime.getMinutes() + 5); 55 | if (!this.accessToken || this.accessTokenExpirationDate <= currentTime) { 56 | return false; 57 | } else { 58 | return true; 59 | } 60 | }; 61 | 62 | Ecobee.prototype.saveCredentials = function(data) { 63 | var currentTime = new Date(); 64 | currentTime.setHours(currentTime.getHours() + 8); 65 | 66 | this.accessToken = data.access_token; 67 | this.accessTokenExpirationDate = currentTime; 68 | this.refreshToken = data.refresh_token; 69 | 70 | this.emit("DeviceEvent", "Connected"); 71 | 72 | if(this.debug) console.log("* Connected to BMW: Access Token - " + this.accessToken); 73 | if(this.debug) console.log("* Connected to BMW: Access Token Time - " + this.accessTokenExpirationDate); 74 | if(this.debug) console.log("* Connected to BMW: Refresh Token - " + this.refreshToken); 75 | } 76 | 77 | Ecobee.prototype.gunzipJSON = function(response, callback) { 78 | var gunzip = zlib.createGunzip(); 79 | var json = ""; 80 | 81 | gunzip.on('data', function(data){ 82 | json += data.toString(); 83 | }); 84 | 85 | gunzip.on('end', function(){ 86 | var data = JSON.parse(json); 87 | callback(data); 88 | }.bind(this)); 89 | 90 | response.pipe(gunzip); 91 | }; 92 | 93 | Ecobee.prototype.login = function(post_data) { 94 | var headers = { 95 | 'Host': 'b2vapi.bmwgroup.us', 96 | 'Content-Type': 'application/x-www-form-urlencoded', 97 | 'Authorization': this.bmwBase64Auth, 98 | 'Accept-Encoding': 'gzip', 99 | 'Content-Length': post_data.length, 100 | 'Referer': this.bmwServerAuthURL.protocol + '//' + this.bmwServerAuthURL.hostname, 101 | 'User-Agent': this.apiAgent 102 | }; 103 | var post_options = { 104 | host: this.bmwServerAuthURL.hostname, 105 | path: this.bmwServerAuthURL.path, 106 | rejectUnauthorized: false, 107 | method: 'POST', 108 | maxRedirects: 3, 109 | headers: headers 110 | }; 111 | 112 | // console.log(headers); 113 | // console.log(post_options); 114 | 115 | var req = https.request(post_options, function(response) { 116 | // console.log(response.statusCode); 117 | this.gunzipJSON(response, function(data) { 118 | this.saveCredentials(data); 119 | this.updateStatus(); 120 | }.bind(this)); 121 | }.bind(this)); 122 | 123 | req.write(post_data); 124 | req.on('error', function(e) {console.log("! Could not connect to BMW to log in (" + e + ")")}); 125 | req.end(); 126 | }; 127 | 128 | // Ecobee.prototype.sendAction = function(action) { 129 | // console.log("* BMW Action:", action) 130 | // var statusString = this.bmwServerCmdString + action; 131 | // var statusURL = url.parse(statusString); 132 | 133 | // var post_data = "serviceType=" + action 134 | // var headers = { 135 | // 'Host': 'b2vapi.bmwgroup.us', 136 | // 'Content-Type': 'application/json;charset=UTF-8', 137 | // 'Authorization': 'Bearer '+this.accessToken, 138 | // 'Accept-Encoding': 'gzip', 139 | // 'Content-Length': post_data.length, 140 | // 'Referer': statusURL.protocol + '//' + statusURL.hostname, 141 | // 'User-Agent': this.apiAgent 142 | // }; 143 | 144 | // var get_options = { 145 | // host: statusURL.hostname, 146 | // path: statusURL.path, 147 | // rejectUnauthorized: false, 148 | // method: 'POST', 149 | // maxRedirects: 3, 150 | // headers: headers 151 | // }; 152 | 153 | // var req = https.request(get_options, function(response) { 154 | // this.gunzipJSON(response, function(data) { 155 | // console.log("* Ecobee: Received response VIN: " + JSON.stringify(data)); 156 | 157 | // }.bind(this)); 158 | // // this.saveState(response); 159 | // }.bind(this)) 160 | 161 | 162 | // req.write(post_data); 163 | // req.on('error', function(e) {console.log("! Could not connect to BMW to set status (" + e + ")")}); 164 | // req.end(); 165 | // } 166 | 167 | module.exports = Ecobee; 168 | -------------------------------------------------------------------------------- /modules/roku/roku.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var http = require('http'); 3 | var util = require('util'); 4 | var xml2js = require('xml2js'); 5 | 6 | Roku.PORT = 8060; 7 | function Roku(data) { 8 | this.host = data.host; 9 | this.getChannels(); 10 | this.eventQueue = []; 11 | this.name = data.name || "Roku"; 12 | } 13 | util.inherits(Roku, EventEmitter); 14 | 15 | Roku.prototype.exec = function(command, params) { 16 | if (command == "LaunchChannel") { 17 | console.log("* Roku Executing: " + command + " : " + params.channel); 18 | this.launchChannel(params.channel); 19 | } else if (command == "SearchRoku") { 20 | console.log("* Roku Search: " + command + " : " + params.forValue); 21 | this.searchRoku(params.forValue); 22 | } else if (command == "Navigate") { 23 | console.log("* Roku Navigate: " + command + " : " + params.forValue); 24 | this.navigateRoku(params.instructions); 25 | } else if (command == "SendEvent") { 26 | this.sendEvent(params.rokuEvent); 27 | } else { 28 | this.sendEvent(command); 29 | } 30 | }; 31 | 32 | Roku.prototype.log = function(data) { 33 | console.log("Roku LOG:" + data); 34 | this.emit("DeviceEvent", "Logged"); 35 | }; 36 | 37 | Roku.prototype.navigateRoku = function (directions) { 38 | var steps = directions.split(" "); 39 | var lastAction; 40 | console.log(steps); 41 | for (var i = 0; i < steps.length; i++) { 42 | var step = steps[i]; 43 | var numberIndex = numbers.indexOf(step); 44 | if (step < 10) { 45 | for (var j = 0; j < parseInt(step); j++) { 46 | this.sendEvent(lastAction); 47 | }; 48 | } else { 49 | this.sendEvent(step); 50 | lastAction = step; 51 | } 52 | }; 53 | } 54 | 55 | Roku.prototype.searchRoku = function (query) { 56 | // New logic for 5.0 57 | this.sendEvent("HOME"); 58 | this.sendEvent("HOME"); 59 | setTimeout(function(){ 60 | // Navigate to search 61 | this.sendEvent("Up"); 62 | this.sendEvent("Up"); 63 | this.sendEvent("Up"); 64 | this.sendEvent("Right"); 65 | // Send query 66 | this.sendText(query); 67 | setTimeout(function(){ 68 | this.sendEvent("Fwd"); 69 | }.bind(this), 3000); 70 | setTimeout(function(){ 71 | this.sendEvent("Right"); 72 | this.sendEvent("Right"); 73 | this.sendEvent("Right"); 74 | }.bind(this), 3500); 75 | }.bind(this), 9000); 76 | }; 77 | 78 | Roku.prototype.launchChannel = function (channelID) { 79 | var request = http.request({ 80 | port : Roku.PORT, 81 | host : this.host, 82 | path : "/launch/" + channelID, 83 | method: 'POST' 84 | }, function(res){ 85 | console.log(res.statusCode); 86 | res.on('data', function (chunk) { 87 | }.bind(this)); 88 | res.on('end', function () { 89 | }.bind(this)); 90 | }.bind(this)); 91 | request.on('error', function(e) {console.log("Error:" + e.message);}); 92 | request.end(); 93 | }; 94 | 95 | Roku.prototype.sendText = function(text) { 96 | var characters = text.split(''); 97 | for (var i = 0; i < characters.length; i++) { 98 | this.sendEvent(characters[i]); 99 | } 100 | }; 101 | 102 | Roku.prototype.sendEvent = function(key) { 103 | try { 104 | if (key.length == 1) key = "Lit_" + escape(key); 105 | var isFirstRequest = this.eventQueue.length === 0; 106 | this.eventQueue.push(key); 107 | if (isFirstRequest) { 108 | setTimeout(this.sendNextEvent.bind(this),333); 109 | } 110 | } catch (e) {} 111 | }; 112 | 113 | Roku.prototype.sendNextEvent = function() { 114 | if (!this.eventQueue.length) return; 115 | var key = this.eventQueue.shift(); 116 | 117 | var request = http.request({ 118 | port : Roku.PORT, 119 | host : this.host, 120 | path : "/keypress/" + key, 121 | method: 'POST' 122 | }, function(res){ 123 | res.on('data', function (chunk) { 124 | }.bind(this)); 125 | res.on('end', function () { 126 | this.sendNextEvent(); 127 | }.bind(this)); 128 | }.bind(this)); 129 | console.log("URL: " + request.path); 130 | request.on('error', function(e) {console.log("Error:" + e.message);}); 131 | request.end(); 132 | }; 133 | 134 | Roku.prototype.getChannels = function() { 135 | var request = http.request({ 136 | port : Roku.PORT, 137 | host : this.host, 138 | path : "/query/apps", 139 | }, function(res) { 140 | res.setEncoding('utf8'); 141 | res.on('data', function (chunk) { 142 | var parser = new xml2js.Parser(); 143 | parser.parseString(chunk, function (err, result) { 144 | var channels = []; 145 | result = result.apps.app; 146 | for (var i = 0; i < result.length; i++) { 147 | var channel = result[i].$; 148 | channel.name = result[i]._; 149 | channels.push(channel); 150 | } 151 | var state = {}; 152 | state["roku." + this.name + ".channels"] = channels; 153 | state["roku." + this.name + ".ip"] = this.host; 154 | this.emit("StateEvent", state); 155 | }.bind(this)); 156 | }.bind(this)); 157 | }.bind(this)); 158 | request.on('error', function(e) { 159 | if (this.debug) console.error("! " + this.name + "\t" + e); 160 | }.bind(this)); 161 | request.end(); 162 | }; 163 | 164 | var dgram = require('dgram'); // dgram is UDP 165 | 166 | // Listen for responses 167 | function listen(port) { 168 | var server = dgram.createSocket("udp4"); 169 | server.on("message", function (msg, rinfo) { 170 | console.log("server got: " + msg + " from " + rinfo.address + ":" + rinfo.port); 171 | }); 172 | 173 | server.bind(port); // Bind to the random port we were given when sending the message, not 1900 174 | 175 | // Give it a while for responses to come in 176 | setTimeout(function(){ 177 | server.close(); 178 | },2000); 179 | } 180 | 181 | module.exports = Roku; 182 | -------------------------------------------------------------------------------- /modules/lutron/lutron.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var net = require('net'); 3 | var util = require('util'); 4 | 5 | // Protocol documentation at: 6 | // http://www.lutron.com/TechnicalDocumentLibrary/RS232ProtocolCommandSet.040196d.pdf 7 | // This may only apply to GRX systems, not QSX or others 8 | 9 | function Lutron(data) { 10 | this.host = data.host; 11 | this.login = data.login || "nwk"; 12 | this.controlUnits = data.controlUnits; 13 | this.keypads = data.keypads; 14 | this.commandQueue = []; 15 | this.scenes = {}; 16 | this.connect(); 17 | this.debug = data.debug; 18 | }; 19 | util.inherits(Lutron, EventEmitter); 20 | 21 | Lutron.prototype.exec = function(command, data) { 22 | console.log("* Lutron Executing: " + command, data); 23 | 24 | if (command == "SetScene") { 25 | var scene = data.scene; 26 | var unit = data.zone || data.controlUnit; 27 | unit = this.controlUnits[unit]; 28 | this.selectScene([unit], scene) 29 | } else if (command == "AllOff") { 30 | this.allOff(); 31 | } else { 32 | } 33 | 34 | }; 35 | 36 | Lutron.prototype.sendCommand = function(string) { 37 | var isFirstRequest = this.commandQueue.length == 0; 38 | this.commandQueue.push(string + "\r\n"); 39 | //if (isFirstRequest) this.sendNextCommand(); 40 | if (isFirstRequest) process.nextTick(this.sendNextCommand.bind(this)); 41 | }; 42 | 43 | Lutron.prototype.sendNextCommand = function() { 44 | if (!this.commandQueue.length) return; 45 | var string = this.commandQueue.shift(); 46 | if (this.debug) console.log("D Lutron >", string); 47 | this.client.write(string, "UTF8", function () { 48 | setTimeout(this.sendNextCommand.bind(this),1000); 49 | }.bind(this)); 50 | } 51 | 52 | Lutron.prototype.sendStatusRequests = function() { 53 | this.sendCommand("G"); 54 | // for (var unit in this.controlUnits) { 55 | // this.sendCommand("rzi " + this.controlUnits[unit]); 56 | // } 57 | } 58 | 59 | Lutron.prototype.allOff = function () { 60 | this.sendCommand("A01234"); 61 | } 62 | 63 | Lutron.prototype.selectScene = function(units, scene) { 64 | this.unlockScenes(); // Do this every time to avoid scene lock, which happens randomly 65 | this.sendCommand("A" + scene + units.join("")); 66 | } 67 | 68 | Lutron.prototype.zoneRaise = function(unit, zones) { 69 | this.sendCommand("B" + unit + zones.join("")); 70 | } 71 | 72 | Lutron.prototype.zoneLower = function(unit, zones) { 73 | this.sendCommand("D" + unit + zones.join("")); 74 | } 75 | 76 | Lutron.prototype.unlockScenes = function() { 77 | this.sendCommand("SL"); 78 | } 79 | 80 | Lutron.prototype.sceneForUnit = function(unit) { 81 | return this.scenes[unit]; 82 | } 83 | 84 | Lutron.prototype.parseData = function(data) { 85 | var parsed = data.match(/~?:?([^ ]*) ?(.*)/); 86 | var command = parsed[1]; 87 | var data = parsed[2]; 88 | 89 | if (this.debug) console.log("D Lutron <", command, data); 90 | 91 | switch (command) { 92 | 93 | case ("zi"): 94 | var levels = data.split(" "); 95 | levels.pop(); 96 | levels.pop(); 97 | var unit = levels.shift(); 98 | for (var i = 0; i < levels.length; i++) { 99 | levels[i] = parseInt(levels[i],16); 100 | if (i && levels[i] == i) levels[i] = null; 101 | }; 102 | var state = {}; 103 | state[("insteon.levels" + unit)] = levels; 104 | this.emit("StateEvent", state); 105 | break; 106 | case ("ss"): 107 | for (var i = 0; i < 8; i++) { 108 | var value = parseInt(data.charAt(i), 16); 109 | if (value != null) this.scenes[i+1] = value; 110 | }; 111 | this.emit("StateEvent", {"insteon.scenes" : this.scenes}); 112 | 113 | if (this.debug) console.log("Scene status:" + JSON.stringify(this.scenes)); 114 | break; 115 | case ("ERROR"): 116 | console.log(data); 117 | break; 118 | default: 119 | if (command.length >=2 && command.length <= 3) { 120 | var code = command.charAt(0); 121 | var key = code.toLowerCase(); 122 | var pressed = (code != key); 123 | var button = command.substring(1); 124 | var name = this.keypads[key]; 125 | this.emit("DeviceEvent", name + "." + button + "." + (pressed ? "Press" : "Release")); 126 | } else if (data == "OK") { 127 | } else { 128 | console.log("! Lutron unknown command:", command); 129 | } 130 | break; 131 | } 132 | } 133 | 134 | // Connection 135 | 136 | Lutron.COM_PORT = 23; 137 | 138 | Lutron.prototype.connect = function() { 139 | this.reconnecting_ = false; 140 | this.client = net.connect({ 141 | host : this.host, 142 | port : Lutron.COM_PORT 143 | }, this.handleConnected.bind(this)); 144 | this.client.setEncoding(); 145 | this.client.on('data', this.handleData.bind(this)); 146 | this.client.on('error', this.handleError.bind(this)); 147 | }; 148 | 149 | Lutron.prototype.reconnect = function() { 150 | if (this.reconnecting_) return; 151 | 152 | this.reconnecting_ = true; 153 | setTimeout(this.connect.bind(this), 10000); 154 | } 155 | 156 | Lutron.prototype.handleConnected = function() { 157 | }; 158 | 159 | Lutron.prototype.handleData = function(data) { 160 | if (data == "login: ") { 161 | this.client.write(this.login + "\r\n", "UTF8"); 162 | } else if (data == "connection established\r\n") { 163 | this.emit("DeviceEvent", "Connected"); 164 | this.sendStatusRequests(); 165 | } else { 166 | this.parseData(data.split("\r\n").shift()); 167 | } 168 | } 169 | 170 | Lutron.prototype.handleError = function(e) { 171 | console.log("! Lutron\t" + e); 172 | this.reconnect(); 173 | }; 174 | 175 | Lutron.prototype.handleEnd = function() { 176 | this.emit("DeviceEvent", "Lutron.Disconnected"); 177 | this.reconnect(); 178 | }; 179 | 180 | Lutron.prototype.log = function(data) { 181 | console.log("Lutron LOG:" + data); 182 | this.emit("DeviceEvent", "Logged"); 183 | } 184 | 185 | module.exports = Lutron; -------------------------------------------------------------------------------- /examples/cambridge/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cambridge 6 | 61 | 62 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
78 |
79 |
LIVING ROOM
80 |
81 |
82 |
83 | 84 |
85 |
KITCHEN
86 |
87 |
88 |
89 |
90 |
91 |
MASTER BED
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
OFFICE
101 |
102 |
103 | 109 |
110 | 111 | 205 | -------------------------------------------------------------------------------- /examples/cambridge/cambridge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is what Glen uses to run his house 3 | */ 4 | var http = require('http'); 5 | var Route = require('route.io'); 6 | 7 | var Lutron = require('route.io-lutron-radiora2'); 8 | var Sonos = require('route.io-sonos'); 9 | var Denon = require('route.io-denon'); 10 | var Web = require('route.io-web'); 11 | var Telnet = require('route.io-telnet'); 12 | 13 | // Map of commands to routers that service that command. 14 | var route = Route.create(); 15 | 16 | var sonos = route.addDevice({ 17 | type : Sonos, 18 | name : "Sonos", 19 | init : { 20 | components : { 21 | "Livingroom" : "10.0.1.3", 22 | "Ethanbed" : "10.0.1.7", 23 | "Masterbed" : "10.0.1.14" 24 | } 25 | } 26 | }); 27 | 28 | var IR = route.addDevice({ 29 | type : Telnet, 30 | name : "IR", 31 | init : { 32 | host : "10.0.1.56", // iTach IP2IR 33 | port: "4998", 34 | commands : { 35 | "A-PC" : "sendir,1:1,1,38109,1,1,342,170,22,21,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,21,21,21,22,64,21,21,22,21,22,21,21,21,22,21,22,63,22,64,21,21,22,64,21,64,22,63,22,64,21,64,22,1518,342,85,22,3810", 36 | "A-AppleTV" : "sendir,1:1,1,38226,1,1,341,171,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,21,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,21,22,63,22,64,21,64,22,63,22,64,21,4892", 37 | "A-ChromeCast" : "sendir,1:1,1,38226,1,1,342,171,22,21,22,21,21,21,22,21,22,21,21,21,22,21,22,21,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,21,22,21,21,21,22,64,21,21,22,21,22,21,21,21,22,64,21,64,22,63,22,21,22,63,22,64,21,64,22,63,22,1523,341,85,22,3822", 38 | "A-PS3" : "sendir,1:1,1,38226,1,1,342,170,22,21,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,21,22,64,21,21,22,21,22,21,21,21,22,21,22,21,21,64,22,21,21,64,22,63,22,64,21,64,22,1522,342,85,22,3822", 39 | "B-PC" : "sendir,1:1,1,38109,1,1,342,170,22,21,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,21,21,21,22,64,21,21,22,64,21,21,22,21,22,21,21,64,22,63,22,21,22,63,22,21,22,63,22,64,21,64,22,1517,342,85,22,3810", 40 | "B-AppleTV" : "sendir,1:1,1,38226,1,1,342,170,22,21,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,21,21,64,22,21,21,21,22,21,22,21,21,21,22,21,22,63,22,21,22,63,22,64,21,64,22,1522,342,85,22,3822", 41 | "B-ChromeCast" : "sendir,1:1,1,38226,1,1,341,171,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,21,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,21,22,21,22,21,21,64,22,63,22,21,22,21,21,21,22,64,21,64,22,63,22,21,22,21,21,64,22,63,22,64,21,1523,342,85,21,3822", 42 | "B-PS3" : "sendir,1:1,1,38226,1,1,341,171,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,21,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,21,22,63,22,64,21,21,22,21,22,21,21,21,22,21,22,63,22,21,22,21,21,64,22,63,22,64,21,1523,342,85,21,3822", 43 | "POWER" : "sendir,1:1,1,38226,1,1,341,171,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,21,22,63,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,64,22,21,21,21,22,21,22,21,21,21,22,21,22,21,21,21,22,64,21,64,22,63,22,64,21,64,22,63,22,64,21,1523,342,85,21,3822", 44 | } 45 | } 46 | }); 47 | 48 | var denon = route.addDevice({ 49 | type : Denon, 50 | name : "Denon", 51 | init : { 52 | host : "10.0.1.20", 53 | sources : { 54 | "HDMI" : "GAME", 55 | "Sonos" : "DVR" 56 | } 57 | } 58 | }); 59 | 60 | var lutron = route.addDevice({ 61 | type : Lutron, 62 | name : "Lutron", 63 | init : { 64 | host : "10.0.1.13", 65 | username : "route", 66 | password: "route", 67 | devices : { 68 | "KitchenBarLights" : { id : 23, type : Lutron.TYPE_LIGHT }, 69 | "KitchenCabinetLights" : { id : 26, type : Lutron.TYPE_LIGHT }, 70 | "KitchenCeilingLights" : { id : 25, type : Lutron.TYPE_LIGHT }, 71 | "KitchenDiningLights" : { id : 27, type : Lutron.TYPE_LIGHT }, 72 | "KitchenKeypad" : { id : 24, type : Lutron.TYPE_KEYPAD}, 73 | "KitchenCoffeeMachine" : { id : 36, type : Lutron.TYPE_LIGHT }, 74 | "LivingroomEntryLight" : { id : 30, type : Lutron.TYPE_LIGHT }, 75 | "LivingroomLoungeLamp" : { id : 18, type : Lutron.TYPE_LIGHT }, 76 | "LivingroomPathLights" : { id : 31, type : Lutron.TYPE_LIGHT }, 77 | "FrontDoorMotion" : { id : 32, type : Lutron.TYPE_MOTION }, 78 | "LivingroomKeypad" : { 79 | id : 14, type : Lutron.TYPE_KEYPAD, 80 | buttons : { 81 | 1 : "AllOn", 82 | 2 : "Pendant", 83 | 3 : "Sonos", 84 | 4 : "LowerShades", 85 | 5 : "Lamp", 86 | 6 : "Goodnight" 87 | } 88 | }, 89 | 90 | "OfficePendantLight" : { id : 15, type : Lutron.TYPE_LIGHT }, 91 | "OfficeKeypad" : { id : 16, type : Lutron.TYPE_LIGHT }, 92 | 93 | "MasterbedRemote" : { id : 29, type : Lutron.TYPE_REMOTE }, 94 | "MasterbedWallLights" : { id : 28, type : Lutron.TYPE_LIGHT }, 95 | "MasterbedKeypad" : { 96 | id : 4, type : Lutron.TYPE_KEYPAD, 97 | buttons : { 98 | 1 : "AllOn", 99 | 2 : "Wall", 100 | 3 : "3", 101 | 4 : "4", 102 | 5 : "Sonos", 103 | 6 : "Goodnight" 104 | } 105 | }, 106 | 107 | "HallwayPendantLights" : { id : 20, type : Lutron.TYPE_LIGHT }, 108 | "HallwayKeypad" : { id : 21, type : Lutron.TYPE_KEYPAD }, 109 | } 110 | } 111 | }); 112 | 113 | var web = route.addDevice({ 114 | type : Web, 115 | name : "Web", 116 | init : { 117 | port : 8000, 118 | dir : __dirname + "/web/" 119 | } 120 | }); 121 | 122 | // Simple map of events to commands. 123 | route.addEventMap({ 124 | // 125 | "Sonos.Livingroom.Started" : "Denon.Switch.Sonos", 126 | 127 | // Hard-coded web switches for media (TV/Speakers) 128 | "Web.Livingroom.Sonos" : "Denon.Switch.Sonos", 129 | "Web.Livingroom.PC" : [ 130 | "IR.A-PC", 131 | "Denon.Switch.HDMI", 132 | "Sonos.Livingroom.Pause", 133 | ], 134 | "Web.Livingroom.AppleTV" : [ 135 | "IR.A-AppleTV", 136 | "Denon.Switch.HDMI", 137 | "Sonos.Livingroom.Pause", 138 | ], 139 | "Web.Livingroom.ChromeCast" : [ 140 | "IR.A-ChromeCast", 141 | "Denon.Switch.HDMI", 142 | "Sonos.Livingroom.Pause", 143 | ], 144 | "Web.Livingroom.PS3" : [ 145 | "IR.A-PS3", 146 | "Denon.Switch.HDMI", 147 | "Sonos.Livingroom.Pause", 148 | ], 149 | 150 | "Web.Masterbed.PC" : "IR.B-PC", 151 | "Web.Masterbed.AppleTV" : "IR.B-AppleTV", 152 | "Web.Masterbed.ChromeCast" : "IR.B-ChromeCast", 153 | "Web.Masterbed.PS3" : "IR.B-PS3", 154 | 155 | "Lutron.LivingroomKeypad.Goodnight.On" : [ 156 | "Sonos.Livingroom.Pause", 157 | "Sonos.Masterbed.Pause", 158 | "Denon.Switch.Sonos", 159 | ], 160 | "Lutron.LivingroomKeypad.Sonos.On" : "Sonos.Livingroom.PlayPause", 161 | "Lutron.MasterbedKeypad.Sonos.On" : "Sonos.Masterbed.PlayPause", 162 | "Web.Livingroom.PlayPause" : "Sonos.Livingroom.PlayPause", 163 | "Web.Masterbed.PlayPause" : "Sonos.Masterbed.PlayPause", 164 | "Web.GlenHome" : [ 165 | "Lutron.LivingroomLoungeLamp.On", 166 | "Lutron.HallwayPendantLights.On", 167 | "Lutron.KitchenBarLights.On", 168 | "Lutron.KitchenCabinetLights.On" 169 | ] 170 | }); 171 | 172 | route.map("Web.Lutron.*", function(eventname, data) { 173 | lutron.exec(eventname.substring(11)); // chop off "Web.Lutron." 174 | }); 175 | 176 | route.map("Web.Sonos.*", function(eventname, data) { 177 | sonos.exec(eventname.substring(10)); // chop off "Web.Sonos." 178 | }); 179 | 180 | route.map("Web.Denon.*", function(eventname, data) { 181 | denon.exec(eventname.substring(10)); // chop off "Web.Denon." 182 | }); 183 | --------------------------------------------------------------------------------