├── 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 |
95 |
98 |
99 |
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 |
--------------------------------------------------------------------------------