Skip to content

Commit

Permalink
Tasmota: Manage distance sensor (#1019)
Browse files Browse the repository at this point in the history
Fixes #1017
  • Loading branch information
atrovato authored Jan 4, 2021
1 parent 48d25c6 commit b745283
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 5 deletions.
12 changes: 10 additions & 2 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,9 @@
"kilowatt-hour": "Kilowatt per hour (kW/h)",
"ampere": "Ampere (A)",
"volt": "Volt (V)",
"ppm": "Parts per million (ppm)"
"ppm": "Parts per million (ppm)",
"cm": "Centimeter (cm)",
"mm": "Millimeter (mm)"
},
"deviceFeatureUnitShort": {
"celsius": "°C",
Expand All @@ -1219,7 +1221,9 @@
"kilowatt-hour": "kW/h",
"ampere": "A",
"volt": "V",
"ppm": "ppm"
"ppm": "ppm",
"cm": "cm",
"mm": "mm"
},
"deviceFeatureCategory": {
"light": {
Expand Down Expand Up @@ -1260,6 +1264,10 @@
"shortCategoryName": "Sismic sensor",
"decimal": "Sismic (decimal)"
},
"distance-sensor": {
"shortCategoryName": "Distance",
"decimal": "Distance"
},
"camera": {
"shortCategoryName": "Camera",
"image": "Camera"
Expand Down
12 changes: 10 additions & 2 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,9 @@
"kilowatt-hour": "Kilowatt heure (kW/h)",
"ampere": "Ampère (A)",
"volt": "Volt (V)",
"ppm": "Partie par million (ppm)"
"ppm": "Partie par million (ppm)",
"cm": "Centimètre (cm)",
"mm": "Millimètre (mm)"
},
"deviceFeatureUnitShort": {
"celsius": "°C",
Expand All @@ -1219,7 +1221,9 @@
"kilowatt-hour": "kW/h",
"ampere": "A",
"volt": "V",
"ppm": "ppm"
"ppm": "ppm",
"cm": "cm",
"mm": "mm"
},
"deviceFeatureCategory": {
"light": {
Expand Down Expand Up @@ -1260,6 +1264,10 @@
"shortCategoryName": "Capteur sismique",
"decimal": "Valeur sismique décimale"
},
"distance-sensor": {
"shortCategoryName": "Distance",
"decimal": "Distance"
},
"camera": {
"shortCategoryName": "Caméra",
"image": "Caméra (Image)"
Expand Down
3 changes: 3 additions & 0 deletions front/src/utils/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ export const DeviceFeatureCategoriesIcon = {
[DEVICE_FEATURE_CATEGORIES.CO2_SENSOR]: {
[DEVICE_FEATURE_TYPES.SENSOR.DECIMAL]: 'bar-chart-2'
},
[DEVICE_FEATURE_CATEGORIES.DISTANCE_SENSOR]: {
[DEVICE_FEATURE_TYPES.SENSOR.DECIMAL]: 'italic'
},
[DEVICE_FEATURE_CATEGORIES.CUBE]: {
[DEVICE_FEATURE_TYPES.CUBE.MODE]: 'activity',
[DEVICE_FEATURE_TYPES.CUBE.ROTATION]: 'rotate-cw'
Expand Down
30 changes: 30 additions & 0 deletions server/services/tasmota/lib/features/distance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const {
DEVICE_FEATURE_CATEGORIES,
DEVICE_FEATURE_TYPES,
DEVICE_FEATURE_UNITS,
} = require('../../../../utils/constants');

module.exports = {
// Tasmota matcher
keyMatcher: /^(StatusSNS|Gladys)\.(SR04|VL53L0X)\.Distance$/,
// Gladys feature
generateFeature: (device, key, value, fullKey) => {
const keyParts = fullKey.split('.');
const unit = keyParts[1] === 'VL53L0X' ? DEVICE_FEATURE_UNITS.MM : DEVICE_FEATURE_UNITS.CM;

return {
category: DEVICE_FEATURE_CATEGORIES.DISTANCE_SENSOR,
type: DEVICE_FEATURE_TYPES.SENSOR.DECIMAL,
name: 'Distance',
read_only: true,
has_feedback: false,
min: 0,
max: 10000,
unit,
};
},
generateExternalId: (key, fullKey) => {
const keyParts = fullKey.split('.');
return `${keyParts[1]}:Distance`;
},
};
4 changes: 3 additions & 1 deletion server/services/tasmota/lib/features/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const modules = require('./modules');
// Features
const power = require('./power');
const dimmer = require('./dimmer');
const distance = require('./distance');
const energyCurrent = require('./energy.current');
const energyPower = require('./energy.power');
const energyVoltage = require('./energy.voltage');
Expand All @@ -21,6 +22,7 @@ const temperature = require('./temperature');
const FEATURE_TEMPLATES = [
power,
dimmer,
distance,
energyCurrent,
energyPower,
energyVoltage,
Expand Down Expand Up @@ -68,7 +70,7 @@ const addFeature = (device, featureTemplate, fullKey, command, value) => {
if (existingFeature) {
logger.debug(`Tasmota: duplicated feature handled for ${externalId}`);
} else {
const generatedFeature = featureTemplate.generateFeature(device, command, value);
const generatedFeature = featureTemplate.generateFeature(device, command, value, fullKey);

if (generatedFeature) {
const convertedValue = generateValue(featureTemplate, value);
Expand Down
43 changes: 43 additions & 0 deletions server/test/services/tasmota/lib/device-creation/SR04.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"STATUS": {
"Status": {
"ButtonRetain": 0,
"ButtonTopic": "0",
"FriendlyName": ["Tasmota"],
"LedMask": "FFFF",
"LedState": 1,
"Module": 18,
"Power": 0,
"PowerOnState": 1,
"PowerRetain": 0,
"SaveData": 1,
"SaveState": 1,
"SensorRetain": 0,
"SwitchMode": [0, 0, 0, 0, 0, 0, 0, 0],
"SwitchRetain": 0,
"SwitchTopic": "0",
"Topic": "tasmota"
}
},
"STATUS11": {
"StatusSTS": {
"MqttCount": 1,
"Sleep": 50,
"SleepMode": "Dynamic",
"Wifi": {
"AP": 1,
"BSSId": "EC:BE:DD:85:1F:E0",
"Channel": 1,
"LinkCount": 1,
"SSId": "ALEX-NETWORK"
}
}
},
"STATUS8": {
"StatusSNS": {
"SR04": {
"Distance": 3.65
}
}
}
}
43 changes: 43 additions & 0 deletions server/test/services/tasmota/lib/device-creation/VL53L0X.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"STATUS": {
"Status": {
"ButtonRetain": 0,
"ButtonTopic": "0",
"FriendlyName": ["Tasmota"],
"LedMask": "FFFF",
"LedState": 1,
"Module": 18,
"Power": 0,
"PowerOnState": 1,
"PowerRetain": 0,
"SaveData": 1,
"SaveState": 1,
"SensorRetain": 0,
"SwitchMode": [0, 0, 0, 0, 0, 0, 0, 0],
"SwitchRetain": 0,
"SwitchTopic": "0",
"Topic": "tasmota"
}
},
"STATUS11": {
"StatusSTS": {
"MqttCount": 1,
"Sleep": 50,
"SleepMode": "Dynamic",
"Wifi": {
"AP": 1,
"BSSId": "EC:BE:DD:85:1F:E0",
"Channel": 1,
"LinkCount": 1,
"SSId": "ALEX-NETWORK"
}
}
},
"STATUS8": {
"StatusSNS": {
"VL53L0X": {
"Distance": 3.65
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
const sinon = require('sinon');
const { expect } = require('chai');

const { fake, assert } = sinon;
const TasmotaHandler = require('../../../../../../../services/tasmota/lib');
const {
DEVICE_FEATURE_CATEGORIES,
DEVICE_FEATURE_TYPES,
DEVICE_FEATURE_UNITS,
EVENTS,
WEBSOCKET_MESSAGE_TYPES,
} = require('../../../../../../../utils/constants');

const messages = require('../../../device-creation/SR04.json');

const mqttService = {
device: {
publish: fake.returns(null),
},
};
const gladys = {
event: {
emit: fake.returns(null),
},
stateManager: {
get: fake.returns(null),
},
};
const serviceId = 'service-uuid-random';

describe('Tasmota - MQTT - create device with SR04 distance features', () => {
const tasmota = new TasmotaHandler(gladys, serviceId);
const tasmotaHandler = tasmota.protocols.mqtt;
tasmotaHandler.mqttService = mqttService;

beforeEach(() => {
sinon.reset();
});

it('decode STATUS message', () => {
tasmotaHandler.handleMessage('stat/tasmota-device-topic/STATUS', JSON.stringify(messages.STATUS));

expect(tasmotaHandler.discoveredDevices).to.deep.eq({});
expect(tasmotaHandler.pendingDevices).to.deep.eq({
'tasmota-device-topic': {
name: 'Tasmota',
params: [
{
name: 'protocol',
value: 'mqtt',
},
],
model: 18,
external_id: 'tasmota:tasmota-device-topic',
selector: 'tasmota-tasmota-device-topic',
service_id: serviceId,
should_poll: false,
features: [],
},
});

assert.notCalled(gladys.event.emit);
assert.notCalled(gladys.stateManager.get);
assert.calledWith(mqttService.device.publish, 'cmnd/tasmota-device-topic/STATUS', '11');
});

it('decode STATUS11 message', () => {
tasmotaHandler.handleMessage('stat/tasmota-device-topic/STATUS11', JSON.stringify(messages.STATUS11));

expect(tasmotaHandler.discoveredDevices).to.deep.eq({});
expect(tasmotaHandler.pendingDevices).to.deep.eq({
'tasmota-device-topic': {
name: 'Tasmota',
params: [
{
name: 'protocol',
value: 'mqtt',
},
],
model: 18,
external_id: 'tasmota:tasmota-device-topic',
selector: 'tasmota-tasmota-device-topic',
service_id: serviceId,
should_poll: false,
features: [],
},
});

assert.notCalled(gladys.event.emit);
assert.notCalled(gladys.stateManager.get);
assert.calledWith(mqttService.device.publish, 'cmnd/tasmota-device-topic/STATUS', '8');
});

it('decode STATUS8 message', () => {
tasmotaHandler.handleMessage('stat/tasmota-device-topic/STATUS8', JSON.stringify(messages.STATUS8));

const expectedDevice = {
name: 'Tasmota',
params: [
{
name: 'protocol',
value: 'mqtt',
},
],
model: 18,
external_id: 'tasmota:tasmota-device-topic',
selector: 'tasmota-tasmota-device-topic',
service_id: serviceId,
should_poll: false,
features: [
{
category: DEVICE_FEATURE_CATEGORIES.DISTANCE_SENSOR,
type: DEVICE_FEATURE_TYPES.SENSOR.DECIMAL,
unit: DEVICE_FEATURE_UNITS.CM,
external_id: 'tasmota:tasmota-device-topic:SR04:Distance',
selector: 'tasmota-tasmota-device-topic-sr04-distance',
name: 'Distance',
read_only: true,
has_feedback: false,
min: 0,
max: 10000,
last_value: 3.65,
},
],
};
expect(tasmotaHandler.discoveredDevices).to.deep.eq({
'tasmota-device-topic': expectedDevice,
});
expect(tasmotaHandler.pendingDevices).to.deep.eq({});

assert.notCalled(mqttService.device.publish);
assert.calledWith(gladys.stateManager.get, 'deviceByExternalId', 'tasmota:tasmota-device-topic');

assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: 'tasmota:tasmota-device-topic:SR04:Distance',
state: 3.65,
});
assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, {
type: WEBSOCKET_MESSAGE_TYPES.TASMOTA.NEW_MQTT_DEVICE,
payload: expectedDevice,
});
});
});
Loading

0 comments on commit b745283

Please sign in to comment.