Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v2 backward compatibility #261

Merged
merged 1 commit into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
868 changes: 868 additions & 0 deletions lambda/smarthome/alexa/v2/ohConnector.js

Large diffs are not rendered by default.

File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class AlexaDirective extends AlexaResponse {
*/
postItemsAndReturn(items, parameters = {}) {
const promises = items.map(item =>
rest.postItemCommand(this.directive.endpoint.scope.token, this.timeout, item.name, item.state));
rest.postItemCommand(this.directive.endpoint.scope.token, item.name, item.state, this.timeout));
Promise.all(promises).then(() => {
this.getPropertiesResponseAndReturn(parameters);
}).catch((error) => {
Expand Down Expand Up @@ -149,7 +149,7 @@ class AlexaDirective extends AlexaResponse {
*/
getItemState(item) {
const itemName = item.sensor || item.name;
return rest.getItem(this.directive.endpoint.scope.token, this.timeout, itemName).then((result) =>
return rest.getItem(this.directive.endpoint.scope.token, itemName, this.timeout).then((result) =>
// Set state to undefined if uninitialized or undefined in oh, otherwise get formatted item state
Object.assign(result, {state: ['NULL', 'UNDEF'].includes(result.state) ? undefined : formatItemState(result)}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class AlexaModeController extends AlexaDirective {
* Set mode
*/
setMode() {
const postItem = Object.assign(this.propertyMap[this.interface].mode.item, {
const postItem = Object.assign({}, this.propertyMap[this.interface].mode.item, {
state: this.directive.payload.mode
});
this.postItemsAndReturn([postItem]);
Expand All @@ -58,7 +58,7 @@ class AlexaModeController extends AlexaDirective {
const index = supportedModes.findIndex(mode => mode === item.state);

// Throw error if current mode not found
if (index === -1 ) {
if (index === -1) {
throw {cause: 'Current mode not found in supported list', item: item, supported: supportedModes};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* Amazon Echo Smart Home Skill API implementation for openHAB (v3)
*/
const camelcase = require('camelcase');
const Directives = require('./alexa/v3');
const Directives = require('./directives');

/**
* Main entry point for all requests
Expand Down
6 changes: 5 additions & 1 deletion lambda/smarthome/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

require('module-alias/register');
const log = require('@lib/log.js');
const ohv3 = require('./ohConnectorV3.js');
const ohv2 = require('./alexa/v2/ohConnector.js');
const ohv3 = require('./alexa/v3/ohConnector.js');

/**
* Main entry point.
Expand All @@ -29,6 +30,9 @@ exports.handler = function (event, context, callback) {
case 3:
ohv3.handleRequest(event.directive, callback);
break;
case 2:
ohv2.handleRequest(event, context);
break;
default:
log.error(`No supported payloadVersion: ${version}`);
callback('No supported payloadVersion.');
Expand Down
16 changes: 8 additions & 8 deletions lambda/smarthome/lib/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,12 @@ function ohAuthenticationSettings(token, options = {}) {
/**
* Returns a single item
* @param {String} token
* @param {Number} timeout
* @param {String} itemName
* @param {Number} timeout
* @return {Promise}
*/
function getItem(token, timeout, itemName) {
return getItemOrItems(token, timeout, itemName);
function getItem(token, itemName, timeout) {
return getItemOrItems(token, itemName, timeout);
}

/**
Expand All @@ -108,18 +108,18 @@ function getItems(token, timeout) {
fields: 'editable,groupNames,groupType,name,label,metadata,stateDescription,tags,type',
metadata: 'alexa,channel,synonyms'
};
return getItemOrItems(token, timeout, null, parameters);
return getItemOrItems(token, null, timeout, parameters);
}

/**
* Returns get item(s) result
* @param {String} token
* @param {Number} timeout
* @param {String} itemName
* @param {Number} timeout
* @param {Object} parameters
* @return {Promise}
*/
function getItemOrItems(token, timeout, itemName, parameters) {
function getItemOrItems(token, itemName, timeout, parameters) {
const options = ohAuthenticationSettings(token, {
method: 'GET',
uri: `${config.openhab.baseURL}/items/${itemName || ''}`,
Expand Down Expand Up @@ -147,12 +147,12 @@ function getRegionalSettings(token, timeout) {
/**
* POST a command to a item
* @param {String} token
* @param {Number} timeout
* @param {String} itemName
* @param {String} value
* @param {Number} timeout
* @return {Promise}
*/
function postItemCommand(token, timeout, itemName, value) {
function postItemCommand(token, itemName, value, timeout) {
const options = ohAuthenticationSettings(token, {
method: 'POST',
uri: `${config.openhab.baseURL}/items/${itemName}`,
Expand Down
34 changes: 34 additions & 0 deletions lambda/smarthome/test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ function generateDirectiveRequest (request) {
if (directive.header.namespace !== 'Alexa.Discovery') {
directive.header.correlationToken = 'correlation-token';
}
// update directive if payloadVersion set to 2
if (directive.header.payloadVersion === '2') {
directive.payload.accessToken = directive.endpoint.scope.token;
delete directive.header.correlationToken;
delete directive.endpoint.scope;
}
// remove endpoint if no id defined
if (directive.endpoint.endpointId === null) {
// move endpoint scope to payload if defined
Expand Down Expand Up @@ -195,6 +201,34 @@ assert.capturedResult = function (result, expected) {
}
};

/**
* Assert discovered appliances (v2)
* @param {Array} appliances
* @param {Object} results
*/
assert.discoveredAppliances = function (appliances, results) {
assert.equal(appliances.length, Object.keys(results).length);

appliances.forEach((appliance) => {
const expected = results[appliance.applianceId];
assert.isDefined(expected);

Object.keys(expected).forEach((key) => {
switch (key) {
case 'actions':
case 'applianceTypes':
assert.sameMembers(appliance[key], expected[key]);
break;
case 'additionalApplianceDetails':
assert.include(appliance[key], expected[key]);
break;
default:
assert.equal(appliance[key], expected[key]);
}
});
});
};

/**
* Asserts discovered endpoints (v3)
* @param {Array} endpoints
Expand Down
40 changes: 40 additions & 0 deletions lambda/smarthome/test/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,46 @@
*/

module.exports = {
"testCasesV2": {
"discovery": {
"Light": [
"./v2/test_discoverLightColor.js",
"./v2/test_discoverLightGroup.js"
],
"Lock": [
"./v2/test_discoverLock.js"
],
"Outlet": [
"./v2/test_discoverOutlet.js"
],
"Switch": [
"./v2/test_discoverSwitchRollershutter.js"
],
"Thermostat": [
"./v2/test_discoverThermostat.js"
],
},
"controllers": {
"Alexa": [
"./v2/test_controllerAlexa.js"
],
"Door Lock": [
"./v2/test_controllerLock.js"
],
"On/Off": [
"./v2/test_controllerOnOff.js"
],
"Percentage": [
"./v2/test_controllerPercentage.js"
],
"Temperature Control": [
"./v2/test_controllerTemperature.js"
],
"Tunable Lighting Control": [
"./v2/test_controllerColor.js"
],
}
},
"testCasesV3": {
"discovery": {
"Fan": [
Expand Down
117 changes: 117 additions & 0 deletions lambda/smarthome/test/test_ohConnectorV2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Copyright (c) 2010-2019 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

require('module-alias/register');
const log = require('@lib/log.js');
const rest = require('@lib/rest.js');
const ohv2 = require('@root/alexa/v2/ohConnector.js');
const settings = require('./settings.js');
const { assert, utils } = require('./common.js');

describe('ohConnectorV2 Tests', function () {

let capture, context, response;

before(function () {
// mock rest external calls
rest.getItem = function () {
return Promise.resolve(
Array.isArray(response.openhab) && response.staged ? response.openhab.shift() : response.openhab);
};
rest.getItems = function () {
return Promise.resolve(
Array.isArray(response.openhab) && response.staged ? response.openhab.shift() : response.openhab);
};
rest.postItemCommand = function (token, itemName, value) {
capture.calls.push({'name': itemName, 'value': value});
return Promise.resolve();
};

// mock log error calls
log.error = function (...args) {
capture.logs.push(
args.map(arg => typeof arg === 'object' ? arg.stack || JSON.stringify(arg) : arg).join(' '));
};

// mock aws lambda context calls
context = {
'succeed': (result) => capture.result = result,
'done': (error, result) => capture.result = result
};
});

beforeEach(function () {
// reset mock variables
response = {};
capture = {'calls': [], 'logs': [], 'result': null};
});

afterEach(function () {
// output log errors if test failed
if (this.currentTest.state === 'failed') {
// eslint-disable-next-line no-console
capture.logs.forEach(message => console.log(message));
}
});

// Discovery Tests
describe('Discovery Messages', function () {
const directive = utils.generateDirectiveRequest({
'header': {
'name': 'DiscoverAppliancesRequest',
'namespace': 'Alexa.ConnectedHome.Discovery',
'payloadVersion': '2'
}
});

Object.keys(settings.testCasesV2.discovery).forEach(function (name) {
settings.testCasesV2.discovery[name].forEach(function (path) {
const test = require(path);

it(test.description, function (done) {
response = {'openhab': test.mocked};
ohv2.handleRequest(directive, context);
// wait for async responses
setTimeout(function () {
// console.log('Appliances: ' + JSON.stringify(capture.result.payload.discoveredAppliances, null, 2));
assert.discoveredAppliances(capture.result.payload.discoveredAppliances, test.expected);
done();
}, 1);
});
});
});
});

// Controller Tests
Object.keys(settings.testCasesV2.controllers).forEach(function (name){
describe(name + ' Messages', function () {
settings.testCasesV2.controllers[name].forEach(function (path){
const tests = require(path);

tests.forEach(function (test) {
it(test.description, function (done) {
response = test.mocked;
ohv2.handleRequest(utils.generateDirectiveRequest(test.directive), context);
// wait for async functions
setTimeout(function () {
// console.log('Capture: ' + JSON.stringify(capture, null, 2));
assert.capturedCalls(capture.calls, test.expected.openhab);
assert.capturedResult(capture.result, test.expected.alexa);
done();
}, 5);
});
});
});
});
});
});
4 changes: 2 additions & 2 deletions lambda/smarthome/test/test_ohConnectorV3.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
require('module-alias/register');
const log = require('@lib/log.js');
const rest = require('@lib/rest.js');
const ohv3 = require('@root/ohConnectorV3.js');
const ohv3 = require('@root/alexa/v3/ohConnector.js');
const settings = require('./settings.js');
const { assert, utils } = require('./common.js');

Expand All @@ -35,7 +35,7 @@ describe('ohConnectorV3 Tests', function () {
rest.getRegionalSettings = function () {
return Promise.resolve(response.settings && response.settings.regional);
};
rest.postItemCommand = function (token, timeout, itemName, value) {
rest.postItemCommand = function (token, itemName, value) {
capture.calls.push({'name': itemName, 'value': value});
return Promise.resolve();
};
Expand Down
39 changes: 39 additions & 0 deletions lambda/smarthome/test/v2/test_controllerAlexa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2019 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

module.exports = [
{
description: "health check request",
directive: {
"header": {
"namespace": "Alexa.ConnectedHome.System",
"name": "HealthCheckRequest",
"payloadVersion": "2"
}
},
mocked: {},
expected: {
alexa: {
"header": {
"namespace": "Alexa.ConnectedHome.System",
"name": "HealthCheckResponse"
},
"payload": {
"description": "The system is currently healthy",
"isHealthy": true
}
},
openhab: []
}
}
];
Loading