Skip to content

Commit

Permalink
Add typedefs
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Krug <michi.krug@gmail.com>
  • Loading branch information
michikrug committed Jan 21, 2022
1 parent bc17738 commit a1ab339
Show file tree
Hide file tree
Showing 61 changed files with 654 additions and 219 deletions.
10 changes: 1 addition & 9 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
{
"env": {
"es6": true,
"es2020": true,
"jest": true,
"node": true
},
"extends": [
"eslint:recommended",
"prettier"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"plugins": [
"prettier"
],
Expand Down
12 changes: 10 additions & 2 deletions functions/apihandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
* @author Michael Krug - Rework
*
*/
/// <reference path="../typedefs.js" />
const http = require('http');
const https = require('https');

class ApiHandler {
/**
* @param {object} config
* @param {Object} config
*/
constructor(config = { host: '', path: '/rest/items/', port: 80 }) {
if (!config.path.startsWith('/')) {
Expand All @@ -46,7 +47,7 @@ class ApiHandler {
}

/**
* @param {object} data
* @param {Object} data
*/
updateCache(data) {
if (data.name) {
Expand All @@ -56,6 +57,7 @@ class ApiHandler {

/**
* @param {string} itemName
* @returns {Item[]}
*/
getFromCache(itemName) {
if (itemName) {
Expand All @@ -70,6 +72,7 @@ class ApiHandler {
* @param {string} method
* @param {string} itemName
* @param {number} length
* @returns {Object}
*/
getOptions(method = 'GET', itemName = '', length = 0) {
const queryString =
Expand Down Expand Up @@ -102,6 +105,7 @@ class ApiHandler {

/**
* @param {string} itemName
* @returns {Promise<Item[]>}
*/
getItem(itemName = '') {
const cached = this.getFromCache(itemName);
Expand Down Expand Up @@ -143,6 +147,9 @@ class ApiHandler {
});
}

/**
* @returns {Promise<Item[]>}
*/
getItems() {
return this.getItem();
}
Expand All @@ -151,6 +158,7 @@ class ApiHandler {
* @param {string} itemName
* @param {string} payload
* @param {string} deviceId
* @returns {Promise}
*/
sendCommand(itemName, payload, deviceId) {
const options = this.getOptions('POST', itemName, payload.length);
Expand Down
1 change: 1 addition & 0 deletions functions/commands/armdisarm.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class ArmDisarm extends DefaultCommand {
return true;
}

// @ts-ignore
validateUpdate(item) {
if (this.deviceType === 'SecuritySystem') {
const securitySystem = new SecuritySystem(item);
Expand Down
146 changes: 114 additions & 32 deletions functions/commands/default.js
Original file line number Diff line number Diff line change
@@ -1,127 +1,163 @@
/* eslint-disable no-unused-vars */
const ackSupported = [
'action.devices.commands.ArmDisarm',
'action.devices.commands.Fill',
'action.devices.commands.LockUnlock',
'action.devices.commands.OnOff',
'action.devices.commands.OpenClose',
'action.devices.commands.ActivateScene',
'action.devices.commands.ThermostatTemperatureSetpoint',
'action.devices.commands.ThermostatTemperatureSetRange',
'action.devices.commands.ThermostatSetMode',
'action.devices.commands.TemperatureRelative'
];

/// <reference path="../../typedefs.js" />
const getDevice = require('../devices').getDevice;

class DefaultCommand {
/**
* @param {object} params
* @param {object} device
* @param {ExecuteIntentCommandExecutionParams} params
* @param {ExecuteIntentCommandDevice} device
* @param {ExecuteIntentCommandExecutionChallenge} [challenge]
*/
constructor(params = {}, device = {}, challenge = {}) {
constructor(params = {}, device = { id: '', customData: {} }, challenge) {
this._params = params;
this._device = device;
this._challenge = challenge;
this._customData = device.customData || {};
}

/**
* @returns {string}
*/
get type() {
return '';
}

/**
* @returns {ExecuteIntentCommandExecutionParams}
*/
get params() {
return this._params;
}

/**
* @returns {ExecuteIntentCommandDevice}
*/
get device() {
return this._device;
}

/**
* @returns {ExecuteIntentCommandExecutionChallenge}
*/
get challenge() {
return this._challenge;
}

/**
* @returns {Object}
*/
get customData() {
return this._customData;
}

/**
* @returns {string}
*/
get itemName() {
return this.device.id;
}

/**
* @returns {string}
*/
get deviceType() {
return this.customData.deviceType || '';
}

/**
* @returns {string}
*/
get itemType() {
return this.customData.itemType || '';
}

/**
* @returns {Object}
*/
get members() {
return this.customData.members || {};
}

/**
* @returns {boolean}
*/
get hasMembers() {
return Object.keys(this.members).length > 0;
}

/**
* @returns {boolean}
*/
get isInverted() {
return !!(this.customData.inverted === true);
}

/**
* @returns {boolean}
*/
get requiresItem() {
return false;
}

/**
* @returns {boolean}
*/
get hasValidParams() {
return true;
}

/**
* @returns {boolean}
*/
get requiresUpdateValidation() {
return false;
}

/**
* Is the requested new state valid
* @param {object} item Current state of item
* @param {Item} item Current state of item
* @returns {boolean} true if state change is valid otherwise throws error
*/
validateStateChange(item) {
return true;
}

get requiresUpdateValidation() {
return false;
}

/**
* Check if new state is as expected
* @param {object} item
* @return {object} Error message if state update failed. Null if all ok.
* @param {Item} item
* @return {ExecuteResponsePayloadCommand|void} Error message if state update failed. Null if all ok
*/
validateUpdate(item) {
return;
}

/**
* @param {object} item
* @param {Item} item
* @returns {string}
*/
convertParamsToValue(item) {
return null;
}

/**
* @param {object} item
* @param {Item} item
* @returns {Object}
*/
getResponseStates(item) {
return {};
}

/*
/**
* Allow individual commands to choose when to enforce the pin
* e.g. Security System only enforcing for disarming but not arming
* @returns {boolean}
*/
get bypassPin() {
return false;
}

/**
* @returns {ExecuteResponsePayloadCommand}
*/
handleAuthPin() {
const pinRequired = this.customData.pinNeeded || this.customData.tfaPin;
const pinReceived = this.challenge && this.challenge.pin;
Expand All @@ -141,7 +177,8 @@ class DefaultCommand {
}

/**
* @param {object} responseStates
* @param {Object} responseStates
* @returns {ExecuteResponsePayloadCommand}
*/
handleAuthAck(responseStates) {
if (!(this.customData.ackNeeded || this.customData.tfaAck) || (this.challenge && this.challenge.ack === true)) {
Expand All @@ -158,6 +195,11 @@ class DefaultCommand {
};
}

/**
* Returns an async timeout of the configured waitForStateChange in seconds
* @returns {Promise<NodeJS.Timeout>}
* @private
*/
async waitForStateChange() {
const secondsToWait = this.customData.waitForStateChange || 0;
if (secondsToWait === 0) {
Expand All @@ -169,6 +211,11 @@ class DefaultCommand {
console.log(`openhabGoogleAssistant - ${this.type}: Finished Waiting`);
}

/**
* Returns an async timeout of the configured waitForStateChange in seconds
* @returns {Promise<ExecuteResponsePayloadCommand>}
* @private
*/
async handleUpdateValidation(apiHandler) {
await this.waitForStateChange();
const item = await apiHandler.getItem(this.device.id);
Expand All @@ -192,14 +239,44 @@ class DefaultCommand {
}
}

get willGetItem() {
const ackWithState =
/**
* returns true if the command requires active acknowledgement
* @returns {boolean}
* @private
*/
get needsAcknowledgement() {
const ackSupported = [
'action.devices.commands.ArmDisarm',
'action.devices.commands.Fill',
'action.devices.commands.LockUnlock',
'action.devices.commands.OnOff',
'action.devices.commands.OpenClose',
'action.devices.commands.ActivateScene',
'action.devices.commands.ThermostatTemperatureSetpoint',
'action.devices.commands.ThermostatTemperatureSetRange',
'action.devices.commands.ThermostatSetMode',
'action.devices.commands.TemperatureRelative'
];
return (
ackSupported.includes(this.type) &&
(this.customData.ackNeeded || this.customData.tfaAck) &&
!(this.challenge && this.challenge.ack);
return this.requiresItem || ackWithState || this.requiresUpdateValidation;
!(this.challenge && this.challenge.ack)
);
}

/**
* @returns {boolean}
* @private
*/
get willGetItem() {
return this.requiresItem || this.needsAcknowledgement || this.requiresUpdateValidation;
}

/**
* @param {Item} item
* @returns {void}
* @private
*/
getMembersAsFallback(item) {
if (this.requiresItem && !this.device.customData.members) {
const deviceType = getDevice(this.device.customData.deviceType);
Expand All @@ -217,6 +294,11 @@ class DefaultCommand {
}
}

/**
* Execute the command including checking for pin, ack and update validation
* @param {Object} apiHandler
* @returns {Promise<ExecuteResponsePayloadCommand>}
*/
async execute(apiHandler) {
const authPinResponse = this.handleAuthPin();
if (authPinResponse) {
Expand Down
3 changes: 2 additions & 1 deletion functions/commands/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference path="../../typedefs.js" />
const glob = require('glob');

const Commands = {};
Expand All @@ -10,7 +11,7 @@ glob.sync('./!(index).js', { cwd: __dirname }).forEach((file) => {
module.exports = {
/**
* @param {string} command
* @param {object} params
* @param {ExecuteIntentCommandExecutionParams} params
*/
getCommandType: (command, params) => {
const commandName = command.split('.')[3].toLowerCase();
Expand Down
Loading

0 comments on commit a1ab339

Please sign in to comment.