diff --git a/front/src/routes/integration/all/ewelink/EweLinkDeviceBox.jsx b/front/src/routes/integration/all/ewelink/EweLinkDeviceBox.jsx
index 96faf9f9f5..03c7dcf9b4 100644
--- a/front/src/routes/integration/all/ewelink/EweLinkDeviceBox.jsx
+++ b/front/src/routes/integration/all/ewelink/EweLinkDeviceBox.jsx
@@ -69,7 +69,7 @@ class EweLinkDeviceBox extends Component {
) {
const validModel = device.features && device.features.length > 0;
const online = device.params.find(param => param.name === DEVICE_ONLINE).value === '1';
- const firmware = device.params.find(param => param.name === DEVICE_FIRMWARE).value;
+ const firmware = (device.params.find(param => param.name === DEVICE_FIRMWARE) || { value: '?.?.?' }).value;
return (
diff --git a/server/services/ewelink/lib/config/ewelink.createClients.js b/server/services/ewelink/lib/config/ewelink.createClients.js
index bff1d45759..68287775cd 100644
--- a/server/services/ewelink/lib/config/ewelink.createClients.js
+++ b/server/services/ewelink/lib/config/ewelink.createClients.js
@@ -11,6 +11,12 @@ function createClients() {
appSecret: applicationSecret,
region: applicationRegion,
});
+
+ this.ewelinkWebSocketClientFactory = new this.eweLinkApi.Ws({
+ appId: applicationId,
+ appSecret: applicationSecret,
+ region: applicationRegion,
+ });
}
module.exports = {
diff --git a/server/services/ewelink/lib/config/ewelink.loadConfiguration.js b/server/services/ewelink/lib/config/ewelink.loadConfiguration.js
index a834f2a10d..6a47c9aaa5 100644
--- a/server/services/ewelink/lib/config/ewelink.loadConfiguration.js
+++ b/server/services/ewelink/lib/config/ewelink.loadConfiguration.js
@@ -42,9 +42,12 @@ async function loadConfiguration() {
}
const tokenObject = JSON.parse(tokens);
+
this.ewelinkWebAPIClient.at = tokenObject.accessToken;
this.ewelinkWebAPIClient.rt = tokenObject.refreshToken;
+ await this.createWebSocketClient();
+
logger.info('eWeLink: stored configuration well loaded...');
} catch (e) {
this.updateStatus({ configured: true, connected: false });
diff --git a/server/services/ewelink/lib/config/ewelink.saveConfiguration.js b/server/services/ewelink/lib/config/ewelink.saveConfiguration.js
index 6bd363abd0..78384162a5 100644
--- a/server/services/ewelink/lib/config/ewelink.saveConfiguration.js
+++ b/server/services/ewelink/lib/config/ewelink.saveConfiguration.js
@@ -28,6 +28,7 @@ async function saveConfiguration({ applicationId = '', applicationSecret = '', a
this.configuration = { applicationId, applicationSecret, applicationRegion };
+ this.closeWebSocketClient();
this.createClients();
this.updateStatus({ configured: true, connected: false });
diff --git a/server/services/ewelink/lib/device/poll.js b/server/services/ewelink/lib/device/poll.js
deleted file mode 100644
index 6813c4a5e3..0000000000
--- a/server/services/ewelink/lib/device/poll.js
+++ /dev/null
@@ -1,88 +0,0 @@
-const { EVENTS, DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../../../utils/constants');
-const { NotFoundError } = require('../../../../utils/coreErrors');
-const { getDeviceParam, setDeviceParam } = require('../../../../utils/device');
-const logger = require('../../../../utils/logger');
-const { readOnlineValue } = require('../features');
-const { pollBinary } = require('../features/binary');
-const { pollHumidity } = require('../features/humidity');
-const { pollTemperature } = require('../features/temperature');
-const { DEVICE_FIRMWARE, DEVICE_ONLINE } = require('../utils/constants');
-const { parseExternalId } = require('../utils/externalId');
-
-/**
- *
- * @description Poll values of an eWeLink device.
- * @param {object} device - The device to poll.
- * @returns {Promise} Promise of nothing.
- * @example
- * poll(device);
- */
-async function poll(device) {
- const { deviceId } = parseExternalId(device.external_id);
- const onlineParam = getDeviceParam(device, DEVICE_ONLINE);
-
- try {
- const { thingList } = await this.handleRequest(async () =>
- this.ewelinkWebAPIClient.device.getThings({ thingList: [{ id: deviceId }] }),
- );
- const [{ itemData: eWeLinkDevice }] = thingList;
- logger.debug('eWeLink: load device: %j', eWeLinkDevice);
-
- const currentOnline = readOnlineValue(eWeLinkDevice.online);
- // if the value is different from the value we have, save new param
- if (onlineParam !== currentOnline) {
- logger.debug(`eWeLink: Polling device "${deviceId}", online new value = ${currentOnline}`);
- setDeviceParam(device, DEVICE_ONLINE, currentOnline);
- }
-
- (device.features || []).forEach((feature) => {
- let state = null;
- switch (feature.category) {
- case DEVICE_FEATURE_CATEGORIES.SWITCH: // Binary
- if (feature.type === DEVICE_FEATURE_TYPES.SWITCH.BINARY) {
- state = pollBinary(eWeLinkDevice, feature);
- }
- break;
- case DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR: // Humidity
- state = pollHumidity(eWeLinkDevice, feature);
- break;
- case DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR: // Temperature
- state = pollTemperature(eWeLinkDevice, feature);
- break;
- default:
- break;
- }
-
- if (state !== null) {
- logger.debug(`eWeLink: Polling device "${deviceId}", emit feature "${feature.external_id}" update`);
- this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
- device_feature_external_id: feature.external_id,
- state,
- });
- }
- });
-
- const firmwareParam = getDeviceParam(device, DEVICE_FIRMWARE);
- if (firmwareParam) {
- const currentVersion = (eWeLinkDevice.params && eWeLinkDevice.params.fwVersion) || false;
- // if the value is different from the value we have, save new param
- if (currentVersion && firmwareParam !== currentVersion) {
- logger.debug(`eWeLink: Polling device "${deviceId}", firmware new value = ${currentVersion}`);
- setDeviceParam(device, DEVICE_FIRMWARE, currentVersion);
- }
- }
- } catch (e) {
- // In case of device not found (also means offline device)
- // we override the "ONLINE" parameter if needed
- if (e instanceof NotFoundError && onlineParam) {
- logger.debug(`eWeLink: Polling device "${deviceId}" can't be found, it seems to be offline`);
- setDeviceParam(device, DEVICE_ONLINE, '0');
- }
-
- throw e;
- }
-}
-
-module.exports = {
- poll,
-};
diff --git a/server/services/ewelink/lib/device/setValue.js b/server/services/ewelink/lib/device/setValue.js
index fa0c2958c3..0e93f971fe 100644
--- a/server/services/ewelink/lib/device/setValue.js
+++ b/server/services/ewelink/lib/device/setValue.js
@@ -1,6 +1,5 @@
const { DEVICE_FEATURE_TYPES } = require('../../../../utils/constants');
const logger = require('../../../../utils/logger');
-const { writeBinaryValue } = require('../features/binary');
const { parseExternalId } = require('../utils/externalId');
/**
@@ -22,7 +21,7 @@ async function setValue(device, deviceFeature, value) {
0,
);
- const binaryValue = writeBinaryValue(value);
+ const binaryValue = value ? 'on' : 'off';
if (nbBinaryFeatures > 1) {
params.switches = [{ switch: binaryValue, outlet: channel }];
} else {
diff --git a/server/services/ewelink/lib/ewelink.stop.js b/server/services/ewelink/lib/ewelink.stop.js
index 344733cf8d..60d41f2605 100644
--- a/server/services/ewelink/lib/ewelink.stop.js
+++ b/server/services/ewelink/lib/ewelink.stop.js
@@ -4,6 +4,7 @@
* await this.stop();
*/
async function stop() {
+ this.closeWebSocketClient();
this.ewelinkWebAPIClient = null;
this.updateStatus({ connected: false });
}
diff --git a/server/services/ewelink/lib/features/binary.js b/server/services/ewelink/lib/features/binary.js
index 005e7831c8..98bbed6f10 100644
--- a/server/services/ewelink/lib/features/binary.js
+++ b/server/services/ewelink/lib/features/binary.js
@@ -1,6 +1,4 @@
const { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES, STATE } = require('../../../../utils/constants');
-const logger = require('../../../../utils/logger');
-const { parseExternalId } = require('../utils/externalId');
module.exports = {
// Gladys feature
@@ -15,22 +13,27 @@ module.exports = {
max: 1,
};
},
- pollBinary: (eWeLinkDevice, feature) => {
- const { deviceId, channel } = parseExternalId(feature.external_id);
- let state = (eWeLinkDevice.params && eWeLinkDevice.params.switch) || false;
- const switches = (eWeLinkDevice.params && eWeLinkDevice.params.switches) || false;
- if (state || switches) {
- if (switches) {
- state = switches[channel - 1].switch;
- }
+ readStates: (externalId, params) => {
+ const states = [];
+
+ // Single switch
+ if (params.switch) {
+ const state = params.switch === 'on' ? STATE.ON : STATE.OFF;
+ states.push({ featureExternalId: `${externalId}:binary:1`, state });
}
- const currentBinaryState = state === 'on' ? STATE.ON : STATE.OFF;
- // if the value is different from the value we have, save new state
- if (state && feature.last_value !== currentBinaryState) {
- logger.debug(`eWeLink: Polling device "${deviceId}", binary new value = ${currentBinaryState}`);
- return currentBinaryState;
+
+ // Multiple switches
+ if (params.switches) {
+ params.switches.forEach(({ switch: value, outlet }) => {
+ const state = value === 'on' ? STATE.ON : STATE.OFF;
+ states.push({
+ featureExternalId: `${externalId}:binary:${outlet}`,
+ state,
+ });
+ });
}
- return null;
+
+ return states;
},
// Gladys vs eWeLink transformers
writeBinaryValue: (value) => {
diff --git a/server/services/ewelink/lib/features/humidity.js b/server/services/ewelink/lib/features/humidity.js
index 062e6201ed..4f3869419c 100644
--- a/server/services/ewelink/lib/features/humidity.js
+++ b/server/services/ewelink/lib/features/humidity.js
@@ -3,12 +3,10 @@ const {
DEVICE_FEATURE_TYPES,
DEVICE_FEATURE_UNITS,
} = require('../../../../utils/constants');
-const logger = require('../../../../utils/logger');
-const { parseExternalId } = require('../utils/externalId');
module.exports = {
// Gladys feature
- generateFeature: (name, channel = 0) => {
+ generateFeature: (name) => {
return {
name: `${name} Humidity`,
category: DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR,
@@ -20,14 +18,17 @@ module.exports = {
unit: DEVICE_FEATURE_UNITS.PERCENT,
};
},
- pollHumidity: (eWeLinkDevice, feature) => {
- const { deviceId } = parseExternalId(feature.external_id);
- const currentHumidity = (eWeLinkDevice.params && eWeLinkDevice.params.currentHumidity) || false;
- // if the value is different from the value we have, save new state
- if (currentHumidity && feature.last_value !== currentHumidity) {
- logger.debug(`eWeLink: Polling device "${deviceId}", humidity new value = ${currentHumidity}`);
- return currentHumidity;
+ readStates: (externalId, params) => {
+ const states = [];
+
+ // Current humidity
+ if (params.currentHumidity) {
+ states.push({
+ featureExternalId: `${externalId}:humidity`,
+ state: params.currentHumidity,
+ });
}
- return null;
+
+ return states;
},
};
diff --git a/server/services/ewelink/lib/features/index.js b/server/services/ewelink/lib/features/index.js
index 60d5676945..5ab067bad2 100644
--- a/server/services/ewelink/lib/features/index.js
+++ b/server/services/ewelink/lib/features/index.js
@@ -1,7 +1,6 @@
-const { DEVICE_POLL_FREQUENCIES } = require('../../../../utils/constants');
const logger = require('../../../../utils/logger');
const { titleize } = require('../../../../utils/titleize');
-const { DEVICE_IP_ADDRESS, DEVICE_FIRMWARE, DEVICE_ONLINE } = require('../utils/constants');
+const { readParams } = require('../params');
const { getExternalId } = require('../utils/externalId');
// Features
@@ -9,6 +8,8 @@ const binaryFeature = require('./binary');
const humidityFeature = require('./humidity');
const temperatureFeature = require('./temperature');
+const AVAILABLE_FEATURES = [binaryFeature, humidityFeature, temperatureFeature];
+
const AVAILABLE_FEATURE_MODELS = {
binary: {
uiid: [1, 2, 3, 4, 5, 6, 7, 8, 9, 14, 15],
@@ -37,17 +38,6 @@ const getDeviceName = (device) => {
return name.trim();
};
-/**
- * @description Convert online state.
- * @param {boolean} online - Online device state.
- * @returns {string} Return the prefix, the device ID and the channel count.
- * @example
- * readOnlineValue(true);
- */
-function readOnlineValue(online) {
- return online ? '1' : '0';
-}
-
/**
* @description Create an eWeLink device for Gladys.
* @param {string} serviceId - The UUID of the service.
@@ -68,22 +58,8 @@ function getDevice(serviceId, device) {
selector: externalId,
features: [],
service_id: serviceId,
- should_poll: true,
- poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_30_SECONDS,
- params: [
- {
- name: DEVICE_IP_ADDRESS,
- value: device.ip || '?.?.?.?',
- },
- {
- name: DEVICE_FIRMWARE,
- value: params.fwVersion || '?.?.?',
- },
- {
- name: DEVICE_ONLINE,
- value: readOnlineValue(device.online),
- },
- ],
+ should_poll: false,
+ params: readParams(device),
};
const deviceUiid = (device.extra || {}).uiid;
@@ -111,7 +87,26 @@ function getDevice(serviceId, device) {
return createdDevice;
}
+/**
+ * @description Read and decode Gladys feature state from eWeLink object.
+ * @param {string} externalId - Device external ID.
+ * @param {object} params - EWeLink received params.
+ * @returns {Array} Arry of featureExternalId / state objects.
+ * @example
+ * const states = readStates('ewelink:10001a', { switch: 'on' });
+ */
+function readStates(externalId, params) {
+ const states = [];
+ AVAILABLE_FEATURES.forEach((feature) => {
+ const updatedStates = feature.readState(externalId, params);
+ updatedStates.forEach((state) => {
+ states.push(state);
+ });
+ });
+ return states;
+}
+
module.exports = {
- readOnlineValue,
getDevice,
+ readStates,
};
diff --git a/server/services/ewelink/lib/features/temperature.js b/server/services/ewelink/lib/features/temperature.js
index 2d8e6703ea..83c20ed64b 100644
--- a/server/services/ewelink/lib/features/temperature.js
+++ b/server/services/ewelink/lib/features/temperature.js
@@ -3,12 +3,10 @@ const {
DEVICE_FEATURE_TYPES,
DEVICE_FEATURE_UNITS,
} = require('../../../../utils/constants');
-const logger = require('../../../../utils/logger');
-const { parseExternalId } = require('../utils/externalId');
module.exports = {
// Gladys feature
- generateFeature: (name, channel = 0) => {
+ generateFeature: (name) => {
return {
name: `${name} Temperature`,
category: DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR,
@@ -20,14 +18,17 @@ module.exports = {
unit: DEVICE_FEATURE_UNITS.CELSIUS,
};
},
- pollTemperature: (eWeLinkDevice, feature) => {
- const { deviceId } = parseExternalId(feature.external_id);
- const currentTemperature = (eWeLinkDevice.params && eWeLinkDevice.params.currentTemperature) || false;
- // if the value is different from the value we have, save new state
- if (currentTemperature && feature.last_value !== currentTemperature) {
- logger.debug(`eWeLink: Polling device "${deviceId}", temperature new value = ${currentTemperature}`);
- return currentTemperature;
+ readStates: (externalId, params) => {
+ const states = [];
+
+ // Current temperature
+ if (params.currentTemperature) {
+ states.push({
+ featureExternalId: `${externalId}:temperature`,
+ state: params.currentTemperature,
+ });
}
- return null;
+
+ return states;
},
};
diff --git a/server/services/ewelink/lib/handlers/ewelink.handleResponse.js b/server/services/ewelink/lib/handlers/ewelink.handleResponse.js
index 3ab0aa77b5..75a4b3ab01 100644
--- a/server/services/ewelink/lib/handlers/ewelink.handleResponse.js
+++ b/server/services/ewelink/lib/handlers/ewelink.handleResponse.js
@@ -20,6 +20,7 @@ async function handleResponse(response) {
case 401:
case 402:
await this.gladys.variable.destroy(CONFIGURATION_KEYS.USER_TOKENS, this.serviceId);
+ this.closeWebSocketClient();
this.updateStatus({ connected: false });
throw new ServiceNotConfiguredError(msg);
case 400:
diff --git a/server/services/ewelink/lib/index.js b/server/services/ewelink/lib/index.js
index 063fd55505..19c0957c30 100644
--- a/server/services/ewelink/lib/index.js
+++ b/server/services/ewelink/lib/index.js
@@ -1,5 +1,4 @@
const { discover } = require('./device/discover');
-const { poll } = require('./device/poll');
const { setValue } = require('./device/setValue');
const { updateStatus } = require('./config/ewelink.updateStatus');
@@ -17,6 +16,13 @@ const { saveTokens } = require('./user/ewelink.saveTokens');
const { handleRequest } = require('./handlers/ewelink.handleRequest');
const { handleResponse } = require('./handlers/ewelink.handleResponse');
+const { createWebSocketClient } = require('./websocket/ewelink.createWebSocketClient');
+const { closeWebSocketClient } = require('./websocket/ewelink.closeWebSocketClient');
+const { onWebSocketMessage } = require('./websocket/ewelink.onWebSocketMessage');
+const { onWebSocketError } = require('./websocket/ewelink.onWebSocketError');
+const { onWebSocketClose } = require('./websocket/ewelink.onWebSocketClose');
+const { onWebSocketOpen } = require('./websocket/ewelink.onWebSocketOpen');
+
const { init } = require('./ewelink.init');
const { stop } = require('./ewelink.stop');
const { upgrade } = require('./versions/ewelink.upgrade');
@@ -35,6 +41,9 @@ const EweLinkHandler = function EweLinkHandler(gladys, eweLinkApi, serviceId) {
this.serviceId = serviceId;
this.ewelinkWebAPIClient = null;
+ this.ewelinkWebSocketClientFactory = null;
+ this.ewelinkWebSocketClient = null;
+
this.loginState = null;
this.configuration = {};
this.status = {
@@ -58,10 +67,16 @@ EweLinkHandler.prototype.handleRequest = handleRequest;
EweLinkHandler.prototype.handleResponse = handleResponse;
EweLinkHandler.prototype.discover = discover;
-EweLinkHandler.prototype.poll = poll;
EweLinkHandler.prototype.setValue = setValue;
EweLinkHandler.prototype.getStatus = getStatus;
+EweLinkHandler.prototype.createWebSocketClient = createWebSocketClient;
+EweLinkHandler.prototype.closeWebSocketClient = closeWebSocketClient;
+EweLinkHandler.prototype.onWebSocketOpen = onWebSocketOpen;
+EweLinkHandler.prototype.onWebSocketClose = onWebSocketClose;
+EweLinkHandler.prototype.onWebSocketError = onWebSocketError;
+EweLinkHandler.prototype.onWebSocketMessage = onWebSocketMessage;
+
EweLinkHandler.prototype.init = init;
EweLinkHandler.prototype.stop = stop;
EweLinkHandler.prototype.upgrade = upgrade;
diff --git a/server/services/ewelink/lib/params/firmware.param.js b/server/services/ewelink/lib/params/firmware.param.js
new file mode 100644
index 0000000000..de586be0f9
--- /dev/null
+++ b/server/services/ewelink/lib/params/firmware.param.js
@@ -0,0 +1,4 @@
+module.exports = {
+ EWELINK_KEY_PATH: 'params.fwVersion',
+ GLADYS_PARAM_KEY: 'FIRMWARE',
+};
diff --git a/server/services/ewelink/lib/params/index.js b/server/services/ewelink/lib/params/index.js
new file mode 100644
index 0000000000..e16581b789
--- /dev/null
+++ b/server/services/ewelink/lib/params/index.js
@@ -0,0 +1,31 @@
+const get = require('get-value');
+
+const onlineParam = require('./online.param');
+const firmwareParam = require('./firmware.param');
+
+const PARAMS = [firmwareParam, onlineParam];
+
+/**
+ * @description Read device params from eWeLink device params.
+ * @param {object} device - Key/value map with updated params.
+ * @returns {Array} Array with all '{ name, value }' device params object.
+ * @example
+ * readParams({ online: true, params: { fwVersion: '2.5.0' } })
+ */
+function readParams(device) {
+ const deviceParams = [];
+
+ PARAMS.forEach(({ EWELINK_KEY_PATH, GLADYS_PARAM_KEY: name, convertValue = (value) => value }) => {
+ const rawValue = get(device, EWELINK_KEY_PATH);
+ if (rawValue !== undefined) {
+ const value = convertValue(rawValue);
+ deviceParams.push({ name, value });
+ }
+ });
+
+ return deviceParams;
+}
+
+module.exports = {
+ readParams,
+};
diff --git a/server/services/ewelink/lib/params/online.param.js b/server/services/ewelink/lib/params/online.param.js
new file mode 100644
index 0000000000..884a4408f0
--- /dev/null
+++ b/server/services/ewelink/lib/params/online.param.js
@@ -0,0 +1,16 @@
+/**
+ * @description Convert online state.
+ * @param {boolean} rawValue - Param raw value.
+ * @returns {string} Converted value to Gladys param value.
+ * @example
+ * const gladysParamValue = convertValue(true);
+ */
+function convertValue(rawValue) {
+ return rawValue ? '1' : '0';
+}
+
+module.exports = {
+ EWELINK_KEY_PATH: 'online',
+ GLADYS_PARAM_KEY: 'ONLINE',
+ convertValue,
+};
diff --git a/server/services/ewelink/lib/user/ewelink.deleteTokens.js b/server/services/ewelink/lib/user/ewelink.deleteTokens.js
index 67de302d2f..cb511258fe 100644
--- a/server/services/ewelink/lib/user/ewelink.deleteTokens.js
+++ b/server/services/ewelink/lib/user/ewelink.deleteTokens.js
@@ -21,9 +21,12 @@ async function deleteTokens() {
// Clear tokens
await this.gladys.variable.destroy(CONFIGURATION_KEYS.USER_TOKENS, this.serviceId);
+
this.ewelinkWebAPIClient.at = null;
this.ewelinkWebAPIClient.rt = null;
+ this.closeWebSocketClient();
+
this.updateStatus({ connected: false });
logger.info('eWeLink: user well disconnected');
}
diff --git a/server/services/ewelink/lib/user/ewelink.exchangeToken.js b/server/services/ewelink/lib/user/ewelink.exchangeToken.js
index 37c24532e2..d9e0ce54f5 100644
--- a/server/services/ewelink/lib/user/ewelink.exchangeToken.js
+++ b/server/services/ewelink/lib/user/ewelink.exchangeToken.js
@@ -31,6 +31,8 @@ async function exchangeToken({ redirectUrl, code, region, state }) {
await this.saveTokens(data);
+ await this.createWebSocketClient();
+
this.updateStatus({ connected: true });
logger.info('eWeLink: user well connected...');
}
diff --git a/server/services/ewelink/lib/utils/constants.js b/server/services/ewelink/lib/utils/constants.js
index f2d3726890..85c5431602 100644
--- a/server/services/ewelink/lib/utils/constants.js
+++ b/server/services/ewelink/lib/utils/constants.js
@@ -9,18 +9,11 @@ const CONFIGURATION_KEYS = {
const DEVICE_SERVICE_ID = 'ewelink';
const DEVICE_EXTERNAL_ID_BASE = 'ewelink';
-const DEVICE_IP_ADDRESS = 'IP_ADDRESS';
-const DEVICE_FIRMWARE = 'FIRMWARE';
-const DEVICE_ONLINE = 'ONLINE';
-
const NB_MAX_RETRY_EXPIRED = 1;
module.exports = {
CONFIGURATION_KEYS,
DEVICE_SERVICE_ID,
DEVICE_EXTERNAL_ID_BASE,
- DEVICE_IP_ADDRESS,
- DEVICE_FIRMWARE,
- DEVICE_ONLINE,
NB_MAX_RETRY_EXPIRED,
};
diff --git a/server/services/ewelink/lib/versions/ewelink.upgrade.js b/server/services/ewelink/lib/versions/ewelink.upgrade.js
index ef624c223a..037034c964 100644
--- a/server/services/ewelink/lib/versions/ewelink.upgrade.js
+++ b/server/services/ewelink/lib/versions/ewelink.upgrade.js
@@ -2,9 +2,10 @@ const Promise = require('bluebird');
const logger = require('../../../../utils/logger');
const v2 = require('./ewelink.v2');
+const v3 = require('./ewelink.v3');
const { CONFIGURATION_KEYS } = require('../utils/constants');
-const VERSIONS = [v2];
+const VERSIONS = [v2, v3];
/**
* @description Upgrades eWeLink integration to last version.
diff --git a/server/services/ewelink/lib/versions/ewelink.v3.js b/server/services/ewelink/lib/versions/ewelink.v3.js
new file mode 100644
index 0000000000..76347f3d06
--- /dev/null
+++ b/server/services/ewelink/lib/versions/ewelink.v3.js
@@ -0,0 +1,42 @@
+const Promise = require('bluebird');
+const logger = require('../../../../utils/logger');
+
+/**
+ * @description Upgrades integration to v3.
+ * @param {object} ewelink - Current eWeLink service handler.
+ * @example
+ * await v3.apply();
+ */
+async function apply(ewelink) {
+ logger.info('eWeLink: load existing devices...');
+
+ // Load eWeLink devices
+ const { serviceId, gladys } = ewelink;
+ const devices = gladys.device.get({ service_id: serviceId });
+
+ await Promise.each(devices, async (device) => {
+ const { params, external_id: externalId } = device;
+ // Update params
+ logger.info('eWeLink: unset IP_ADDRESS and FIRMWARE="?.?.?" parameters from "%s" device...', externalId);
+ // - remove 'IP_ADDRESS' param
+ // - remove 'FIRMWARE' with '?.?.?' value
+ const clearedParams = params.filter((param) => {
+ return !(param.name === 'IP_ADDRESS' || (param.name === 'FIRMWARE' && param.value === '?.?.?'));
+ });
+ device.params = clearedParams;
+
+ // Remove poll information
+ logger.info('eWeLink: unset polling from "%s" device...', externalId);
+ device.should_poll = false;
+ delete device.poll_frequency;
+
+ // Save device
+ logger.info('eWeLink: saving cleared "%s" device...', externalId);
+ await gladys.device.create(device);
+ });
+}
+
+module.exports = {
+ apply,
+ VERSION_NUMBER: 3,
+};
diff --git a/server/services/ewelink/lib/websocket/ewelink.closeWebSocketClient.js b/server/services/ewelink/lib/websocket/ewelink.closeWebSocketClient.js
new file mode 100644
index 0000000000..d1303823b5
--- /dev/null
+++ b/server/services/ewelink/lib/websocket/ewelink.closeWebSocketClient.js
@@ -0,0 +1,15 @@
+/**
+ * @description Close WebSocket client.
+ * @example
+ * this.closeWebSocketClient();
+ */
+function closeWebSocketClient() {
+ if (this.ewelinkWebSocketClient !== null) {
+ this.ewelinkWebSocketClient.close();
+ this.ewelinkWebSocketClient = null;
+ }
+}
+
+module.exports = {
+ closeWebSocketClient,
+};
diff --git a/server/services/ewelink/lib/websocket/ewelink.createWebSocketClient.js b/server/services/ewelink/lib/websocket/ewelink.createWebSocketClient.js
new file mode 100644
index 0000000000..9ca5444e1c
--- /dev/null
+++ b/server/services/ewelink/lib/websocket/ewelink.createWebSocketClient.js
@@ -0,0 +1,23 @@
+/**
+ * @description Create WebSocket client.
+ * @example
+ * await this.createWebSocketClient();
+ */
+async function createWebSocketClient() {
+ const { applicationId: appId, applicationRegion: region } = this.configuration;
+ this.ewelinkWebSocketClient = await this.ewelinkWebSocketClientFactory.Connect.create(
+ {
+ appId,
+ region,
+ at: this.ewelinkWebAPIClient.at,
+ },
+ this.onWebSocketOpen.bind(this),
+ this.onWebSocketClose.bind(this),
+ this.onWebSocketError.bind(this),
+ this.onWebSocketMessage.bind(this),
+ );
+}
+
+module.exports = {
+ createWebSocketClient,
+};
diff --git a/server/services/ewelink/lib/websocket/ewelink.onWebSocketClose.js b/server/services/ewelink/lib/websocket/ewelink.onWebSocketClose.js
new file mode 100644
index 0000000000..a3fd3bf0fe
--- /dev/null
+++ b/server/services/ewelink/lib/websocket/ewelink.onWebSocketClose.js
@@ -0,0 +1,15 @@
+const logger = require('../../../../utils/logger');
+
+/**
+ * @description Action to execute when WebSocket is closed.
+ * @example
+ * this.onWebSocketClose();
+ */
+function onWebSocketClose() {
+ this.closeWebSocketClient();
+ logger.info('eWeLink: WebSocket is closed');
+}
+
+module.exports = {
+ onWebSocketClose,
+};
diff --git a/server/services/ewelink/lib/websocket/ewelink.onWebSocketError.js b/server/services/ewelink/lib/websocket/ewelink.onWebSocketError.js
new file mode 100644
index 0000000000..b8f5e6d965
--- /dev/null
+++ b/server/services/ewelink/lib/websocket/ewelink.onWebSocketError.js
@@ -0,0 +1,15 @@
+const logger = require('../../../../utils/logger');
+
+/**
+ * @description Action to execute when WebSocket is on error state.
+ * @param {object} error - WebSocket error.
+ * @example
+ * this.onWebSocketError();
+ */
+function onWebSocketError(error) {
+ logger.error('eWeLink: WebSocket is on error: %s', error.message);
+}
+
+module.exports = {
+ onWebSocketError,
+};
diff --git a/server/services/ewelink/lib/websocket/ewelink.onWebSocketMessage.js b/server/services/ewelink/lib/websocket/ewelink.onWebSocketMessage.js
new file mode 100644
index 0000000000..e5294c97df
--- /dev/null
+++ b/server/services/ewelink/lib/websocket/ewelink.onWebSocketMessage.js
@@ -0,0 +1,58 @@
+const Promise = require('bluebird');
+
+const logger = require('../../../../utils/logger');
+const { EVENTS } = require('../../../../utils/constants');
+const { setDeviceParam } = require('../../../../utils/device');
+
+const { getExternalId } = require('../utils/externalId');
+const { readStates } = require('../features');
+const { readParams } = require('../params');
+
+/**
+ * @description Action to execute when WebSocket receives a message.
+ * @param {object} ws - Current WebSocket client.
+ * @param {object} message - WebSocket message.
+ * @example
+ * this.onWebSocketMessage();
+ */
+async function onWebSocketMessage(ws, message) {
+ logger.debug('eWeLink: WebSocket received a message: %j', message);
+
+ await this.handleResponse(message);
+
+ const { deviceid, params } = message;
+ const externalId = getExternalId({ deviceid });
+
+ // Load device to update params
+ const device = this.gladys.stateManager.get('deviceByExternalId', externalId);
+ if (!device) {
+ logger.info(`eWeLink: device "${deviceid} not found in Gladys`);
+ } else {
+ // Update the device feature values
+ const states = readStates(externalId, params);
+ states.forEach(({ featureExternalId, state }) => {
+ // Before sending event, check if feature exists
+ const feature = this.gladys.stateManager.get('deviceFeatureByExternalId', featureExternalId);
+ if (feature) {
+ this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
+ device_feature_external_id: featureExternalId,
+ state,
+ });
+ } else {
+ logger.debug(`eWeLink: feature ${featureExternalId} not found in Gladys`);
+ }
+ });
+
+ // Update the device params
+ const updatedParams = readParams(params);
+ // Update device params
+ await Promise.each(updatedParams, async ({ key, value }) => {
+ setDeviceParam(device, key, value);
+ await this.gladys.device.setParam(device, key, value);
+ });
+ }
+}
+
+module.exports = {
+ onWebSocketMessage,
+};
diff --git a/server/services/ewelink/lib/websocket/ewelink.onWebSocketOpen.js b/server/services/ewelink/lib/websocket/ewelink.onWebSocketOpen.js
new file mode 100644
index 0000000000..c551fbce5b
--- /dev/null
+++ b/server/services/ewelink/lib/websocket/ewelink.onWebSocketOpen.js
@@ -0,0 +1,14 @@
+const logger = require('../../../../utils/logger');
+
+/**
+ * @description Action to execute when WebSocket is open.
+ * @example
+ * this.onWebSocketOpen();
+ */
+function onWebSocketOpen() {
+ logger.info('eWeLink: WebSocket is ready');
+}
+
+module.exports = {
+ onWebSocketOpen,
+};
diff --git a/server/services/ewelink/package-lock.json b/server/services/ewelink/package-lock.json
index 586eb3bf10..3c90491585 100644
--- a/server/services/ewelink/package-lock.json
+++ b/server/services/ewelink/package-lock.json
@@ -19,7 +19,8 @@
],
"dependencies": {
"bluebird": "^3.7.2",
- "ewelink-api-next": "^1.0.3"
+ "ewelink-api-next": "^1.0.3",
+ "get-value": "^3.0.1"
}
},
"node_modules/@leichtgewicht/ip-codec": {
@@ -196,6 +197,17 @@
"node": ">=6 <7 || >=8"
}
},
+ "node_modules/get-value": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-3.0.1.tgz",
+ "integrity": "sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -209,6 +221,14 @@
"node": ">=0.8.19"
}
},
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -556,6 +576,14 @@
"universalify": "^0.1.0"
}
},
+ "get-value": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-3.0.1.tgz",
+ "integrity": "sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==",
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -566,6 +594,11 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
},
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
+ },
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
diff --git a/server/services/ewelink/package.json b/server/services/ewelink/package.json
index f6460103e3..0eb3f02141 100644
--- a/server/services/ewelink/package.json
+++ b/server/services/ewelink/package.json
@@ -14,6 +14,7 @@
],
"dependencies": {
"bluebird": "^3.7.2",
- "ewelink-api-next": "^1.0.3"
+ "ewelink-api-next": "^1.0.3",
+ "get-value": "^3.0.1"
}
}
diff --git a/server/test/services/ewelink/lib/config/ewelink.loadConfiguration.test.js b/server/test/services/ewelink/lib/config/ewelink.loadConfiguration.test.js
index 9d1ff197b8..95bc480b3e 100644
--- a/server/test/services/ewelink/lib/config/ewelink.loadConfiguration.test.js
+++ b/server/test/services/ewelink/lib/config/ewelink.loadConfiguration.test.js
@@ -1,7 +1,7 @@
const { expect } = require('chai');
const sinon = require('sinon');
-const { assert, fake, stub } = sinon;
+const { assert, fake, stub, match } = sinon;
const EwelinkHandler = require('../../../../../services/ewelink/lib');
const { SERVICE_ID } = require('../constants');
@@ -11,9 +11,15 @@ const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../../utils/consta
describe('eWeLinkHandler loadConfiguration', () => {
let eWeLinkHandler;
let eWeLinkApiMock;
+ let eWeLinkWsMock;
let gladys;
beforeEach(() => {
+ eWeLinkWsMock = stub();
+ eWeLinkWsMock.prototype.Connect = {
+ create: stub(),
+ };
+
gladys = {
event: {
emit: fake.returns(null),
@@ -25,6 +31,7 @@ describe('eWeLinkHandler loadConfiguration', () => {
eWeLinkApiMock = {
WebAPI: stub(),
+ Ws: eWeLinkWsMock,
};
eWeLinkHandler = new EwelinkHandler(gladys, eWeLinkApiMock, SERVICE_ID);
});
@@ -52,6 +59,7 @@ describe('eWeLinkHandler loadConfiguration', () => {
assert.calledWithExactly(gladys.variable.getValue, 'APPLICATION_REGION', SERVICE_ID);
assert.notCalled(eWeLinkApiMock.WebAPI);
+ assert.notCalled(eWeLinkApiMock.Ws);
}
});
@@ -80,6 +88,7 @@ describe('eWeLinkHandler loadConfiguration', () => {
assert.calledWithExactly(gladys.variable.getValue, 'APPLICATION_REGION', SERVICE_ID);
assert.notCalled(eWeLinkApiMock.WebAPI);
+ assert.notCalled(eWeLinkApiMock.Ws);
}
});
@@ -110,6 +119,7 @@ describe('eWeLinkHandler loadConfiguration', () => {
assert.calledWithExactly(gladys.variable.getValue, 'APPLICATION_REGION', SERVICE_ID);
assert.notCalled(eWeLinkApiMock.WebAPI);
+ assert.notCalled(eWeLinkApiMock.Ws);
}
});
@@ -151,6 +161,13 @@ describe('eWeLinkHandler loadConfiguration', () => {
appSecret: 'APPLICATION_SECRET_VALUE',
region: 'APPLICATION_REGION_VALUE',
});
+ assert.calledOnceWithExactly(eWeLinkApiMock.Ws, {
+ appId: 'APPLICATION_ID_VALUE',
+ appSecret: 'APPLICATION_SECRET_VALUE',
+ region: 'APPLICATION_REGION_VALUE',
+ });
+
+ assert.notCalled(eWeLinkWsMock.prototype.Connect.create);
}
});
@@ -188,6 +205,21 @@ describe('eWeLinkHandler loadConfiguration', () => {
appSecret: 'APPLICATION_SECRET_VALUE',
region: 'APPLICATION_REGION_VALUE',
});
+ assert.calledOnceWithExactly(eWeLinkApiMock.Ws, {
+ appId: 'APPLICATION_ID_VALUE',
+ appSecret: 'APPLICATION_SECRET_VALUE',
+ region: 'APPLICATION_REGION_VALUE',
+ });
+
+ assert.calledOnce(eWeLinkWsMock.prototype.Connect.create);
+ assert.calledWithMatch(
+ eWeLinkWsMock.prototype.Connect.create,
+ match({ appId: 'APPLICATION_ID_VALUE', region: 'APPLICATION_REGION_VALUE', at: 'ACCESS_TOKEN' }),
+ match.func,
+ match.func,
+ match.func,
+ match.func,
+ );
expect(eWeLinkHandler.ewelinkWebAPIClient.at).eq('ACCESS_TOKEN');
expect(eWeLinkHandler.ewelinkWebAPIClient.rt).eq('REFRESH_TOKEN');
diff --git a/server/test/services/ewelink/lib/config/ewelink.saveConfiguration.test.js b/server/test/services/ewelink/lib/config/ewelink.saveConfiguration.test.js
index 891439945f..6111dc12f7 100644
--- a/server/test/services/ewelink/lib/config/ewelink.saveConfiguration.test.js
+++ b/server/test/services/ewelink/lib/config/ewelink.saveConfiguration.test.js
@@ -26,6 +26,7 @@ describe('eWeLinkHandler saveConfiguration', () => {
eWeLinkApiMock = {
WebAPI: stub(),
+ Ws: stub(),
};
eWeLinkHandler = new EwelinkHandler(gladys, eWeLinkApiMock, SERVICE_ID);
});
@@ -48,6 +49,8 @@ describe('eWeLinkHandler saveConfiguration', () => {
assert.notCalled(gladys.variable.destroy);
expect(eWeLinkHandler.ewelinkWebAPIClient).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClientFactory).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClient).eq(null);
});
it('should throw a BadParameter error as SECRET and REGION variables are mossing', async () => {
@@ -64,6 +67,8 @@ describe('eWeLinkHandler saveConfiguration', () => {
assert.notCalled(gladys.variable.destroy);
expect(eWeLinkHandler.ewelinkWebAPIClient).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClientFactory).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClient).eq(null);
});
it('should throw a BadParameter error as REGION variables is missing', async () => {
@@ -80,6 +85,8 @@ describe('eWeLinkHandler saveConfiguration', () => {
assert.notCalled(gladys.variable.destroy);
expect(eWeLinkHandler.ewelinkWebAPIClient).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClientFactory).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClient).eq(null);
});
it('should throw a error on database store failure', async () => {
@@ -106,6 +113,8 @@ describe('eWeLinkHandler saveConfiguration', () => {
assert.notCalled(gladys.variable.destroy);
expect(eWeLinkHandler.ewelinkWebAPIClient).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClientFactory).eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClient).eq(null);
});
it('should save configuration and send events', async () => {
@@ -132,5 +141,7 @@ describe('eWeLinkHandler saveConfiguration', () => {
assert.calledOnceWithExactly(gladys.variable.destroy, 'USER_TOKENS', SERVICE_ID);
expect(eWeLinkHandler.ewelinkWebAPIClient).not.eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClientFactory).not.eq(null);
+ expect(eWeLinkHandler.ewelinkWebSocketClient).eq(null);
});
});
diff --git a/server/test/services/ewelink/lib/device/poll.test.js b/server/test/services/ewelink/lib/device/poll.test.js
deleted file mode 100644
index bca0279d0b..0000000000
--- a/server/test/services/ewelink/lib/device/poll.test.js
+++ /dev/null
@@ -1,105 +0,0 @@
-const { expect } = require('chai');
-const sinon = require('sinon');
-
-const { EVENTS } = require('../../../../../utils/constants');
-const EwelinkHandler = require('../../../../../services/ewelink/lib');
-
-const { SERVICE_ID, EWELINK_DENIED_ACCESS_TOKEN } = require('../constants');
-const Gladys2ChDevice = require('../payloads/Gladys-2ch.json');
-const GladysBasicDevice = require('../payloads/Gladys-Basic.json');
-const GladysOfflineDevice = require('../payloads/Gladys-offline.json');
-const GladysPowDevice = require('../payloads/Gladys-pow.json');
-const GladysThDevice = require('../payloads/Gladys-th.json');
-const EweLinkApiMock = require('../ewelink-api.mock.test');
-
-const { assert, fake } = sinon;
-
-describe('eWeLinkHandler poll', () => {
- let eWeLinkHandler;
- let gladys;
-
- beforeEach(() => {
- gladys = {
- event: {
- emit: fake.resolves(null),
- },
- };
-
- eWeLinkHandler = new EwelinkHandler(gladys, EweLinkApiMock, SERVICE_ID);
- eWeLinkHandler.ewelinkWebAPIClient = new EweLinkApiMock.WebAPI();
- eWeLinkHandler.status = { configured: true, connected: true };
- });
-
- afterEach(() => {
- sinon.reset();
- });
-
- it('should poll device and emit 2 states for a "2CH" model', async () => {
- await eWeLinkHandler.poll(Gladys2ChDevice);
- assert.callCount(gladys.event.emit, 2);
- assert.calledWithExactly(gladys.event.emit.getCall(0), EVENTS.DEVICE.NEW_STATE, {
- device_feature_external_id: 'ewelink:10004531ae:binary:1',
- state: 1,
- });
- assert.calledWithExactly(gladys.event.emit.getCall(1), EVENTS.DEVICE.NEW_STATE, {
- device_feature_external_id: 'ewelink:10004531ae:binary:2',
- state: 0,
- });
- });
- it('should poll device and emit 1 state for a "POW" device', async () => {
- await eWeLinkHandler.poll(GladysPowDevice);
- assert.calledOnceWithExactly(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
- device_feature_external_id: 'ewelink:10004533ae:binary:1',
- state: 1,
- });
- });
- it('should poll device and emit 3 states for a "TH" model', async () => {
- await eWeLinkHandler.poll(GladysThDevice);
- assert.callCount(gladys.event.emit, 3);
- assert.calledWithExactly(gladys.event.emit.getCall(0), EVENTS.DEVICE.NEW_STATE, {
- device_feature_external_id: 'ewelink:10004534ae:binary:1',
- state: 1,
- });
- assert.calledWithExactly(gladys.event.emit.getCall(1), EVENTS.DEVICE.NEW_STATE, {
- device_feature_external_id: 'ewelink:10004534ae:humidity',
- state: 42,
- });
- assert.calledWithExactly(gladys.event.emit.getCall(2), EVENTS.DEVICE.NEW_STATE, {
- device_feature_external_id: 'ewelink:10004534ae:temperature',
- state: 20,
- });
- });
- it('should poll device and update 2 params for a "Basic" model', async () => {
- // this check that some values are set on the device, and will be changed
- expect(GladysBasicDevice.params).to.deep.equal([
- { name: 'IP_ADDRESS', value: '192.168.0.6' },
- { name: 'FIRMWARE', value: '3.1.2' },
- { name: 'ONLINE', value: '0' },
- ]);
- await eWeLinkHandler.poll(GladysBasicDevice);
- assert.notCalled(gladys.event.emit);
- expect(GladysBasicDevice.params).to.deep.equal([
- { name: 'IP_ADDRESS', value: '192.168.0.6' },
- { name: 'FIRMWARE', value: '3.3.0' },
- { name: 'ONLINE', value: '1' },
- ]);
- });
- it('should throw an error when device is offline', async () => {
- try {
- await eWeLinkHandler.poll(GladysOfflineDevice);
- assert.fail();
- } catch (error) {
- expect(error.message).to.equal('eWeLink: Error, device is not currently online');
- }
- });
- it('should throw an error when AccessToken is no more valid', async () => {
- eWeLinkHandler.ewelinkWebAPIClient.at = EWELINK_DENIED_ACCESS_TOKEN;
- try {
- await eWeLinkHandler.poll(Gladys2ChDevice);
- assert.fail();
- } catch (error) {
- expect(error).instanceOf(Error);
- expect(error.message).to.equal('eWeLink: Authentication error');
- }
- });
-});
diff --git a/server/test/services/ewelink/lib/ewelink-api.mock.test.js b/server/test/services/ewelink/lib/ewelink-api.mock.test.js
index 64ffc7823a..6e1d84d401 100644
--- a/server/test/services/ewelink/lib/ewelink-api.mock.test.js
+++ b/server/test/services/ewelink/lib/ewelink-api.mock.test.js
@@ -87,6 +87,27 @@ class WebAPI {
}
}
-const items = { WebAPI, Device };
+class Connect {
+ constructor(root) {
+ this.root = root;
+
+ this.create = () => {};
+ }
+}
+
+class Ws {
+ constructor(options = { appId: EWELINK_APP_ID, appSecret: EWELINK_APP_SECRET, region: EWELINK_APP_REGION }) {
+ this.appId = options.appId;
+ this.appSecret = options.appSecret;
+ this.region = options.region;
+
+ // default with right access token
+ this.at = EWELINK_VALID_ACCESS_TOKEN;
+
+ this.Connect = new Connect(this);
+ }
+}
+
+const items = { WebAPI, Device, Ws, Connect };
module.exports = { default: items, ...items };
diff --git a/server/test/services/ewelink/lib/features/features.test.js b/server/test/services/ewelink/lib/features/features.test.js
index 0741b03ab6..0e7251d4eb 100644
--- a/server/test/services/ewelink/lib/features/features.test.js
+++ b/server/test/services/ewelink/lib/features/features.test.js
@@ -24,17 +24,6 @@ describe('eWeLink features parseExternalId', () => {
});
});
-describe('eWeLink features readOnlineValue', () => {
- it('should return 1 if device is online', () => {
- const value = features.readOnlineValue(true);
- expect(value).to.equal('1');
- });
- it('should return 0 if device is offline', () => {
- const value = features.readOnlineValue(false);
- expect(value).to.equal('0');
- });
-});
-
describe('eWeLink features getDevice', () => {
it('should return device without features if offline', () => {
const device = features.getDevice(SERVICE_ID, eweLinkOfflineDevice);
diff --git a/server/test/services/ewelink/lib/params/online.param.test.js b/server/test/services/ewelink/lib/params/online.param.test.js
new file mode 100644
index 0000000000..b0f7e20312
--- /dev/null
+++ b/server/test/services/ewelink/lib/params/online.param.test.js
@@ -0,0 +1,15 @@
+const { expect } = require('chai');
+
+const onlineParam = require('../../../../../services/ewelink/lib/params/online.param');
+
+describe('eWeLink online param', () => {
+ it('should return 1 if device is online', () => {
+ const value = onlineParam.convertValue(true);
+ expect(value).to.equal('1');
+ });
+
+ it('should return 0 if device is offline', () => {
+ const value = onlineParam.convertValue(false);
+ expect(value).to.equal('0');
+ });
+});
diff --git a/server/test/services/ewelink/lib/payloads/Gladys-2ch.json b/server/test/services/ewelink/lib/payloads/Gladys-2ch.json
index a5b0efc6ad..3267189b09 100644
--- a/server/test/services/ewelink/lib/payloads/Gladys-2ch.json
+++ b/server/test/services/ewelink/lib/payloads/Gladys-2ch.json
@@ -4,8 +4,7 @@
"model": "2CH",
"external_id": "ewelink:10004531ae",
"selector": "ewelink:10004531ae",
- "should_poll": true,
- "poll_frequency": 30000,
+ "should_poll": false,
"features": [
{
"name": "Test 1 Ch1 On/Off",
@@ -31,10 +30,6 @@
}
],
"params": [
- {
- "name": "IP_ADDRESS",
- "value": "192.168.0.1"
- },
{
"name": "FIRMWARE",
"value": "3.3.0"
diff --git a/server/test/services/ewelink/lib/payloads/Gladys-Basic.json b/server/test/services/ewelink/lib/payloads/Gladys-Basic.json
index 9eeda592b3..896a6f51cc 100644
--- a/server/test/services/ewelink/lib/payloads/Gladys-Basic.json
+++ b/server/test/services/ewelink/lib/payloads/Gladys-Basic.json
@@ -4,8 +4,7 @@
"model": "Basic",
"external_id": "ewelink:10004536ae",
"selector": "ewelink:10004536ae",
- "should_poll": true,
- "poll_frequency": 30000,
+ "should_poll": false,
"features": [
{
"name": "Test 6 On/Off",
@@ -31,10 +30,6 @@
}
],
"params": [
- {
- "name": "IP_ADDRESS",
- "value": "192.168.0.6"
- },
{
"name": "FIRMWARE",
"value": "3.1.2"
diff --git a/server/test/services/ewelink/lib/payloads/Gladys-offline.json b/server/test/services/ewelink/lib/payloads/Gladys-offline.json
index 0f5bc8fb24..1ecd98bb95 100644
--- a/server/test/services/ewelink/lib/payloads/Gladys-offline.json
+++ b/server/test/services/ewelink/lib/payloads/Gladys-offline.json
@@ -4,18 +4,9 @@
"model": "MINI",
"external_id": "ewelink:10004532ae",
"selector": "ewelink:10004532ae",
- "should_poll": true,
- "poll_frequency": 30000,
+ "should_poll": false,
"features": [],
"params": [
- {
- "name": "IP_ADDRESS",
- "value": "?.?.?.?"
- },
- {
- "name": "FIRMWARE",
- "value": "?.?.?"
- },
{
"name": "ONLINE",
"value": "0"
diff --git a/server/test/services/ewelink/lib/payloads/Gladys-pow.json b/server/test/services/ewelink/lib/payloads/Gladys-pow.json
index d77f7b9424..7664064c48 100644
--- a/server/test/services/ewelink/lib/payloads/Gladys-pow.json
+++ b/server/test/services/ewelink/lib/payloads/Gladys-pow.json
@@ -4,8 +4,7 @@
"model": "Pow",
"external_id": "ewelink:10004533ae",
"selector": "ewelink:10004533ae",
- "should_poll": true,
- "poll_frequency": 30000,
+ "should_poll": false,
"features": [
{
"name": "Test 3 On/Off",
@@ -20,10 +19,6 @@
}
],
"params": [
- {
- "name": "IP_ADDRESS",
- "value": "192.168.0.3"
- },
{
"name": "FIRMWARE",
"value": "3.3.0"
diff --git a/server/test/services/ewelink/lib/payloads/Gladys-th.json b/server/test/services/ewelink/lib/payloads/Gladys-th.json
index 293f771298..03f8e821e0 100644
--- a/server/test/services/ewelink/lib/payloads/Gladys-th.json
+++ b/server/test/services/ewelink/lib/payloads/Gladys-th.json
@@ -4,8 +4,7 @@
"model": "TH",
"external_id": "ewelink:10004534ae",
"selector": "ewelink:10004534ae",
- "should_poll": true,
- "poll_frequency": 30000,
+ "should_poll": false,
"features": [
{
"name": "Test 4 On/Off",
@@ -44,10 +43,6 @@
}
],
"params": [
- {
- "name": "IP_ADDRESS",
- "value": "192.168.0.4"
- },
{
"name": "FIRMWARE",
"value": "3.1.2"
diff --git a/server/test/services/ewelink/lib/payloads/Gladys-unhandled.json b/server/test/services/ewelink/lib/payloads/Gladys-unhandled.json
index 9400bb3e94..a06f24770e 100644
--- a/server/test/services/ewelink/lib/payloads/Gladys-unhandled.json
+++ b/server/test/services/ewelink/lib/payloads/Gladys-unhandled.json
@@ -4,18 +4,9 @@
"model": "UNKNOWN",
"external_id": "ewelink:10004535ae",
"selector": "ewelink:10004535ae",
- "should_poll": true,
- "poll_frequency": 30000,
+ "should_poll": false,
"features": [],
"params": [
- {
- "name": "IP_ADDRESS",
- "value": "?.?.?.?"
- },
- {
- "name": "FIRMWARE",
- "value": "?.?.?"
- },
{
"name": "ONLINE",
"value": "1"
diff --git a/server/test/services/ewelink/lib/user/ewelink.exchangeToken.test.js b/server/test/services/ewelink/lib/user/ewelink.exchangeToken.test.js
index f4f736a314..214ed0f785 100644
--- a/server/test/services/ewelink/lib/user/ewelink.exchangeToken.test.js
+++ b/server/test/services/ewelink/lib/user/ewelink.exchangeToken.test.js
@@ -1,10 +1,10 @@
const { expect } = require('chai');
const sinon = require('sinon');
-const { fake, assert } = sinon;
+const { fake, assert, match } = sinon;
const EwelinkHandler = require('../../../../../services/ewelink/lib');
-const { SERVICE_ID } = require('../constants');
+const { SERVICE_ID, EWELINK_APP_ID, EWELINK_APP_REGION } = require('../constants');
const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../../utils/constants');
const { BadParameters } = require('../../../../../utils/coreErrors');
@@ -30,11 +30,20 @@ describe('eWeLinkHandler exchangeToken', () => {
getToken: fake.resolves({ data: tokens }),
},
};
+ eWeLinkHandler.ewelinkWebSocketClientFactory = {
+ Connect: {
+ create: fake.returns({}),
+ },
+ };
eWeLinkHandler.status = {
configured: true,
connected: false,
};
eWeLinkHandler.loginState = 'LOGIN_STATE';
+ eWeLinkHandler.configuration = {
+ applicationId: EWELINK_APP_ID,
+ applicationRegion: EWELINK_APP_REGION,
+ };
});
afterEach(() => {
@@ -56,6 +65,7 @@ describe('eWeLinkHandler exchangeToken', () => {
}
assert.notCalled(eWeLinkHandler.ewelinkWebAPIClient.oauth.getToken);
+ assert.notCalled(eWeLinkHandler.ewelinkWebSocketClientFactory.Connect.create);
assert.notCalled(gladys.variable.setValue);
assert.notCalled(gladys.event.emit);
});
@@ -73,6 +83,15 @@ describe('eWeLinkHandler exchangeToken', () => {
redirectUrl,
region,
});
+ assert.calledOnce(eWeLinkHandler.ewelinkWebSocketClientFactory.Connect.create);
+ assert.calledWithMatch(
+ eWeLinkHandler.ewelinkWebSocketClientFactory.Connect.create,
+ match({ appId: EWELINK_APP_ID, region: EWELINK_APP_REGION, at: tokens.accessToken }),
+ match.func,
+ match.func,
+ match.func,
+ match.func,
+ );
assert.calledOnceWithExactly(gladys.variable.setValue, 'USER_TOKENS', JSON.stringify(tokens), SERVICE_ID);
assert.calledOnceWithExactly(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, {
type: WEBSOCKET_MESSAGE_TYPES.EWELINK.STATUS,
diff --git a/server/test/services/ewelink/lib/version/ewelink.upgrade.test.js b/server/test/services/ewelink/lib/version/ewelink.upgrade.test.js
index a3c3bbebd1..0ff4a7e49a 100644
--- a/server/test/services/ewelink/lib/version/ewelink.upgrade.test.js
+++ b/server/test/services/ewelink/lib/version/ewelink.upgrade.test.js
@@ -5,6 +5,73 @@ const { stub, assert } = sinon;
const EwelinkHandler = require('../../../../../services/ewelink/lib');
const { SERVICE_ID } = require('../constants');
+const gladysDevices = [
+ {
+ externalId: 'ewelink:1',
+ should_poll: true,
+ poll_frequency: 30000,
+ features: [{ type: 'binary', category: 'switch' }],
+ params: [
+ {
+ name: 'ONLINE',
+ value: '1',
+ },
+ {
+ name: 'FIRMWARE',
+ value: '?.?.?',
+ },
+ {
+ name: 'IP_ADDRESS',
+ value: 'xx.xx.xx.xx.xx',
+ },
+ ],
+ },
+ {
+ externalId: 'ewelink:2',
+ should_poll: false,
+ features: [{ type: 'binary', category: 'light' }],
+ params: [
+ {
+ name: 'ONLINE',
+ value: '1',
+ },
+ {
+ name: 'FIRMWARE',
+ value: '3.2.1',
+ },
+ ],
+ },
+];
+
+const expectedGladysDevices = [
+ {
+ externalId: 'ewelink:1',
+ should_poll: false,
+ features: [{ type: 'binary', category: 'switch' }],
+ params: [
+ {
+ name: 'ONLINE',
+ value: '1',
+ },
+ ],
+ },
+ {
+ externalId: 'ewelink:2',
+ should_poll: false,
+ features: [{ type: 'binary', category: 'light' }],
+ params: [
+ {
+ name: 'ONLINE',
+ value: '1',
+ },
+ {
+ name: 'FIRMWARE',
+ value: '3.2.1',
+ },
+ ],
+ },
+];
+
describe('eWeLinkHandler init', () => {
let eWeLinkHandler;
let gladys;
@@ -16,6 +83,10 @@ describe('eWeLinkHandler init', () => {
setValue: stub(),
destroy: stub(),
},
+ device: {
+ get: stub().resolves(gladysDevices),
+ create: stub().resolves({}),
+ },
};
eWeLinkHandler = new EwelinkHandler(gladys, null, SERVICE_ID);
@@ -41,12 +112,19 @@ describe('eWeLinkHandler init', () => {
assert.calledOnceWithExactly(gladys.variable.getValue, 'SERVICE_VERSION', SERVICE_ID);
assert.callCount(gladys.variable.destroy, 3);
- assert.callCount(gladys.variable.setValue, 1);
+ assert.callCount(gladys.variable.setValue, 2);
// v2
assert.calledWithExactly(gladys.variable.destroy, 'EWELINK_EMAIL', SERVICE_ID);
assert.calledWithExactly(gladys.variable.destroy, 'EWELINK_PASSWORD', SERVICE_ID);
assert.calledWithExactly(gladys.variable.destroy, 'EWELINK_REGION', SERVICE_ID);
assert.calledWithExactly(gladys.variable.setValue, 'SERVICE_VERSION', '2', SERVICE_ID);
+
+ // v3
+ assert.calledOnceWithExactly(gladys.device.get, { service_id: SERVICE_ID });
+ assert.callCount(gladys.device.create, 2);
+ assert.calledWithExactly(gladys.device.create, expectedGladysDevices[0]);
+ assert.calledWithExactly(gladys.device.create, expectedGladysDevices[1]);
+ assert.calledWithExactly(gladys.variable.setValue, 'SERVICE_VERSION', '3', SERVICE_ID);
});
});
diff --git a/server/utils/setDeviceFeature.js b/server/utils/setDeviceFeature.js
index 12e5368165..63895010b4 100644
--- a/server/utils/setDeviceFeature.js
+++ b/server/utils/setDeviceFeature.js
@@ -4,7 +4,7 @@
* @param {object} feature - The feature to add.
* @returns {object} The device.
* @example
- * setDeviceParam({ features: [] }, { selector: 'feature' })
+ * setDeviceFeature({ features: [] }, { selector: 'feature' })
*/
function setDeviceFeature(device, feature) {
let { features } = device;