Skip to content

Commit

Permalink
Merge pull request #116 from Nase00/direct-indirect-toggle
Browse files Browse the repository at this point in the history
Direct indirect toggle
  • Loading branch information
sowiecki committed Nov 17, 2016
2 parents bdc3c7d + 5bf25e0 commit 94d67d1
Show file tree
Hide file tree
Showing 20 changed files with 324 additions and 147 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,20 @@ Minimum required hardware **per each remote module**:
[![Photograph of hardware wiring](./wiring_photo_small.png)](./wiring_photo.png)

#### Photon Boards
[Connect each board](https://docs.particle.io/guide/getting-started/start/photon/) to your WiFi network, then flash each with the [VoodooSpark firmware](https://github.com/voodootikigod/voodoospark).
There are two ways to configure boards.

**Direct (LAN-only):** Modules are controlled over a LAN using
[particle-io](https://www.npmjs.com/package/particle-io) and [Johnny-Five](johnny-five.io).

**Indirect:** Modules runs directly on the Particle cloud and receive updates through the [Particle API for JS](https://www.npmjs.com/package/particle-api-js).
Motion and temperature features currently do not function in indirect mode.

[Connect each board](https://docs.particle.io/guide/getting-started/start/photon/) to your WiFi network.

If running in **direct** mode, flash each with the [VoodooSpark firmware](https://github.com/voodootikigod/voodoospark).

If running in **indirect** mode, flash each module with `firmware/gtfo-indirect.ino`.
(`npm run flash` is a WIP script to flash every device listed in `devices.json`.)

Retrieve the access tokens and device ids for each Photon, and place them into `environment/devices.json`.
See [environment configuration documentation](./environment/README.md).
Expand Down Expand Up @@ -153,4 +166,5 @@ npm run test # Lints and tests client, server, and universal code.
--mocks # Disables Outlook api in favor of using mock reservation data.
--dhc # Disables consoleController's fancy terminal output, sometimes needed for debugging.
--dd # Disables devices, useful for client testing without room module hardware.
--indirect # Enables direct mode. Overrides setting in config.json.
```
23 changes: 12 additions & 11 deletions environment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ All user-configurated files are (and should remain) gitignored to prevent privat
### config.json
General configuration.

| Parameter | Description | Required? |
|----------------------|---------------------------------------------|-----------|
| public | Configurations passed to client (insecure!) | Yes |
| - title | Title to display on client | No |
| - enableTemperature | Enables (experimental) temperature readings | No |
| - enableMotion | Enables (experimental) motion readings | No |
| - defaultTempScale | Must be either 'celcius' or 'fahrenheit' | No |
| emailDomain | @yourCompany.com | Yes |
| prodReservationsHost | URL of hosted ems_wrapper instance | Yes |
| prodStallsHost | URL of hosted stalls service instance | Yes |
| proxyHost | URL of hosted proxy instance | Yes |
| Parameter | Description | Required? | Default |
|----------------------|---------------------------------------------|-----------|---------|
| public | Configurations passed to client (insecure!) | Yes | |
| - title | Page title to display on client | No | |
| - enableTemperature | Enables (experimental) temperature readings | No | |
| - enableMotion | Enables (experimental) motion readings | No | |
| - defaultTempScale | Must be either 'celcius' or 'fahrenheit' | No | |
| emailDomain | @yourCompany.com | Yes | |
| prodReservationsHost | URL of hosted ems_wrapper instance | Yes | |
| prodStallsHost | URL of hosted stalls service instance | Yes | |
| proxyHost | URL of hosted proxy instance | Yes | |
| indirect | Run modules in indirect mode | No | `false` |

Example of a `config.json`:
```json
Expand Down
3 changes: 2 additions & 1 deletion environment/schemas/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export default {
},
prodReservationsHost: { type: 'string' },
prodStallsHost: { type: 'string' },
proxyHost: { type: 'string' }
proxyHost: { type: 'string' },
indirect: { type: 'bool' }
},
required: ['public', 'prodReservationsHost']
},
Expand Down
33 changes: 33 additions & 0 deletions firmware/flash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint no-console:0 */
/* globals console */

require('babel-core/register');

const Particle = require('particle-api-js');
const path = require('path');
const colors = require('colors');
const { devices } = require('../server/environment');

const particle = new Particle();

const FIRMATA_PATH = path.join(__dirname, './gtfo-indirect.ino');

devices.forEach((device) => {
const flash = particle.flashDevice({
deviceId: device.deviceId,
auth: device.deviceAuthToken,
files: {
file1: FIRMATA_PATH
}
});

flash.then((data) => {
console.log('Device flash result:', JSON.stringify(data));
const deviceName = colors.green.bold(device.name);
console.log(`${deviceName} flashing started successfully!`);
}, (err) => {
const bodyError = colors.red.bold(err.body.error);
const deviceName = colors.magenta.bold(device.name);
console.log(`${deviceName} failed to flash: ${bodyError}`);
});
});
60 changes: 60 additions & 0 deletions firmware/gtfo-indirect.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "rgb-controls/rgb-controls.h"
using namespace RGBControls;

Led led(A7, A5, A4, false);

Color standby1(10, 10, 10);
Color standby2(50, 50, 50);

Color purple(150, 0, 150);
Color green(0, 255, 0);
Color greenSoft(25, 255, 25);

Color orange(255, 148, 0);
Color orangeRed(255, 63, 0);
Color red(255, 0, 0);

Color blue(0, 0, 255);
Color blueSoft(50, 50, 255);

Color colors[2] = { standby1, standby2 };

int fadeRate = 2000;

void setup() {
led.setColor(standby1);
Particle.function("status", handleStatus);
}

int handleStatus(String status) {
if (status == "BOOKED") {
fadeRate = 2000;
led.fadeOnce(colors[1], blue, 2000);
colors[0] = blue;
colors[1] = blueSoft;
return 1;
} else if (status == "VACANT") {
fadeRate = 2000;
led.fadeOnce(colors[1], green, 2000);
colors[0] = green;
colors[1] = greenSoft;
return 1;
} else if (status == "FIVE_MINUTE_WARNING") {
fadeRate = 1500;
led.fadeOnce(colors[1], orange, 2000);
colors[0] = orange;
colors[1] = orangeRed;
return 1;
} else if (status == "ONE_MINUTE_WARNING") {
fadeRate = 500;
colors[0] = red;
colors[1] = orange;
return 1;
}

return 0;
}

void loop() {
led.fade(colors, 2, fadeRate);
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gtfo",
"version": "1.0.0",
"version": "1.1.0",
"description": "Gently Tell Folks Out of meeting rooms - remote unit control",
"repository": {
"type": "git",
Expand All @@ -9,6 +9,7 @@
"main": "application.js",
"node_test_paths": "./tests/server/**/*.spec.* ./tests/universal/**/*.spec.*",
"scripts": {
"flash": "node firmware/flash",
"clean": "rimraf server/public/dist",
"demo": "node environment/demo",
"deploy-server": "NODE_ENV=production forever start ./application.js",
Expand Down Expand Up @@ -70,7 +71,7 @@
"material-ui": "^0.16.0",
"moment": "^2.16.0",
"morgan": "^1.7.0",
"particle-api-js": "^6.2.0",
"particle-api-js": "^6.4.0",
"particle-io": "^0.14.0",
"radium": "^0.18.0",
"react": "^15.3.0",
Expand All @@ -80,7 +81,7 @@
"react-router": "^3.0.0",
"react-router-redux": "^4.0.2",
"react-swipeable-views": "^0.8.0",
"react-tap-event-plugin": "^1.0.0",
"react-tap-event-plugin": "^2.0.0",
"recompose": "^0.20.2",
"redux": "^3.5.2",
"request": "^2.74.0",
Expand Down
7 changes: 7 additions & 0 deletions server/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ if (argv.dd) {
console.log(colors.gray.italic('Devices disabled'));
}

if (config.indirect || argv.indirect) {
process.env.RUN_MODE = 'runIndirect';
console.log(colors.gray.italic('Running in indirect mode\n'));
} else {
process.env.RUN_MODE = 'runDirect';
}

export const isProd = process.env.NODE_ENV === 'production';
export const isTest = process.env.NODE_ENV === 'test';
export const enableproxy = !!config.proxyHost;
Expand Down
3 changes: 3 additions & 0 deletions server/constants/hardware.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export const RUN_DIRECT = 'runDirect';
export const RUN_INDIRECT = 'runIndirect';

export const RGB_PINS = {
red: 'A7',
green: 'A5',
Expand Down
115 changes: 79 additions & 36 deletions server/controllers/devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,45 @@
* Registers accessories for each device
*/

import Particle from 'particle-api-js';
import { isEmpty } from 'lodash';
import colors from 'colors';

import consoleController from './console';
import store from '../store';
import { config } from '../environment';
import { registerBoard,
registerLed,
registerPiezo,
registerThermo,
registerMotion } from '../utils';
registerMotion,
secureRooms } from '../utils';
import { EMIT_INIT_SOCKETS } from '../ducks/clients';
import { FETCH_ROOM_RESERVATIONS,
FETCH_ROOM_TEMPERATURE,
FETCH_ROOM_MOTION,
EMIT_SET_ROOM_ACCESSORIES,
EMIT_ROOM_MODULE_FAILURE } from '../ducks/rooms';
import { CHECK_INTERVAL } from '../constants';
import { CHECK_INTERVAL, RUN_DIRECT } from '../constants';

const particle = new Particle();

const devicesController = {
getRooms() {
const { rooms } = store.getState().roomsReducer.toJS();
getRooms: () => store.getState().roomsReducer.toJS().rooms,

return rooms;
},
getSecureRooms: () => secureRooms(devicesController.getRooms()),

getReservations: (req, res) => res.json(devicesController.getSecureRooms()),

/**
* Kicks off setting up and connecting to devices.
* If devices are disabled, fetches reservations early without starting devices.
* @returns {undefined}
*/
initialize() {
const devicesEnabled = !process.env.DISABLE_DEVICES;
const runningDirect = process.env.RUN_MODE === RUN_DIRECT;

store.dispatch({
type: EMIT_INIT_SOCKETS,
publicConfig: config.public
Expand All @@ -43,27 +53,6 @@ const devicesController = {
const fetchRoomReservations = () => store.dispatch({ type: FETCH_ROOM_RESERVATIONS });
fetchRoomReservations();

if (process.env.DISABLE_DEVICES) {
return;
}

devicesController.getRooms().map((room) => {
const board = registerBoard(room);

board.on('ready', () => devicesController.boardReady(board, room));
board.on('warn', consoleController.logBoardWarn);
board.on('fail', (event) => {
consoleController.logBoardFail(event);
devicesController.boardFail(room);
});
});

// Catches exceptions caused by individual modules, keeping system online
process.on('uncaughtException', (error) => {
console.log('Exception caught');
console.info(error.stack);
});

// Set interval for checking and responding to room state
const monitorExternalServices = setInterval(() => {
fetchRoomReservations();
Expand All @@ -73,11 +62,65 @@ const devicesController = {
clearInterval(monitorExternalServices);
}
}, CHECK_INTERVAL);

if (devicesEnabled && runningDirect) {
devicesController.updateDirect();
}
},

updateDirect() {
devicesController.getRooms().map((room) => {
if (!isEmpty(room.deviceAuthToken)) {
const board = registerBoard(room);

board.on('ready', () => devicesController.boardReady(board, room));
board.on('warn', consoleController.logBoardWarn);
board.on('fail', (event) => {
consoleController.logBoardFail(event);
devicesController.boardFail(room);
});
}
});

// Catches exceptions caused by individual modules, keeping system online
process.on('uncaughtException', (error) => {
consoleController.log('Exception caught');
consoleController.log(error.stack);
});
},

updateIndirect(rooms) {
rooms.forEach((room) => {
particle.callFunction({
deviceId: room.get('deviceId'),
auth: room.get('deviceAuthToken'),
name: 'status',
argument: room.get('alert')
}).then((data) => {
const deviceName = colors.green.bold(room.get('name'));
consoleController.log(`Successfully updated status of ${deviceName}`);

store.dispatch({
type: EMIT_SET_ROOM_ACCESSORIES,
room: room.toJS(),
connectionStatus: data.body.connected
});
}, (err) => {
const bodyError = colors.red.bold(err.body.error);
const deviceName = colors.magenta.bold(room.get('name'));
consoleController.log(`${err.errorDescription} @${deviceName} ${bodyError}`);

store.dispatch({
type: EMIT_ROOM_MODULE_FAILURE,
room,
connectionStatus: false
});
});
});
},

/**
* Top-level scope for handling an individual room's
* board accessories and reservations.
* Handle an individual room's board accessories and reservations.
* Kicks off actions to monitor accessory states, updating server state as necessary.
* @param {object} board JohnnyFive board object.
* @param {object} room Corresponding room object.
Expand All @@ -98,7 +141,8 @@ const devicesController = {
store.dispatch({
type: EMIT_SET_ROOM_ACCESSORIES,
room,
accessories
accessories,
connectionStatus: true
});

if (config.public.enableTemperature) {
Expand All @@ -118,12 +162,11 @@ const devicesController = {
}
},

boardFail(room) {
store.dispatch({
type: EMIT_ROOM_MODULE_FAILURE,
room
});
}
boardFail: (room) => store.dispatch({
type: EMIT_ROOM_MODULE_FAILURE,
room,
connectionStatus: false
})
};

export default devicesController;
Loading

0 comments on commit 94d67d1

Please sign in to comment.