Skip to content

Commit

Permalink
Expose options (Koenkk#3154)
Browse files Browse the repository at this point in the history
* Update

* Updates

* Updates

* Updates

* Updates
  • Loading branch information
Koenkk authored Oct 23, 2021
1 parent 8c3d572 commit 1159051
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 12 deletions.
82 changes: 80 additions & 2 deletions converters/fromZigbee.js

Large diffs are not rendered by default.

21 changes: 16 additions & 5 deletions converters/toZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ const converters = {
},
light_brightness_step: {
key: ['brightness_step', 'brightness_step_onoff'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
const onOff = key.endsWith('_onoff');
const command = onOff ? 'stepWithOnOff' : 'step';
Expand Down Expand Up @@ -653,6 +654,7 @@ const converters = {
},
light_colortemp_step: {
key: ['color_temp_step'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
value = Number(value);
if (isNaN(value)) {
Expand Down Expand Up @@ -718,6 +720,7 @@ const converters = {
},
light_hue_saturation_step: {
key: ['hue_step', 'saturation_step'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
value = Number(value);
if (isNaN(value)) {
Expand Down Expand Up @@ -775,6 +778,7 @@ const converters = {
},
light_onoff_brightness: {
key: ['state', 'brightness', 'brightness_percent'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
const {message} = meta;
const transition = utils.getTransition(entity, 'brightness', meta);
Expand Down Expand Up @@ -879,7 +883,7 @@ const converters = {
},
light_colortemp: {
key: ['color_temp', 'color_temp_percent'],
options: [exposes.options.color_sync()],
options: [exposes.options.color_sync(), exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
const [colorTempMin, colorTempMax] = light.findColorTempRange(entity, meta.logger);
const preset = {'warmest': colorTempMax, 'warm': 454, 'neutral': 370, 'cool': 250, 'coolest': colorTempMin};
Expand Down Expand Up @@ -946,7 +950,7 @@ const converters = {
},
light_color: {
key: ['color'],
options: [exposes.options.color_sync()],
options: [exposes.options.color_sync(), exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
let command;
const newColor = libColor.Color.fromConverterArg(value);
Expand Down Expand Up @@ -1044,6 +1048,7 @@ const converters = {
* converter is used to do just that.
*/
key: ['color', 'color_temp', 'color_temp_percent'],
options: [exposes.options.color_sync(), exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
if (key == 'color') {
const result = await converters.light_color.convertSet(entity, key, value, meta);
Expand Down Expand Up @@ -1662,6 +1667,7 @@ const converters = {
},
gledopto_light_onoff_brightness: {
key: ['state', 'brightness', 'brightness_percent'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
if (meta.message && meta.message.hasOwnProperty('transition')) {
meta.message.transition = meta.message.transition * 3.3;
Expand All @@ -1684,6 +1690,7 @@ const converters = {
},
gledopto_light_colortemp: {
key: ['color_temp', 'color_temp_percent'],
options: [exposes.options.color_sync(), exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
if (meta.message && meta.message.hasOwnProperty('transition')) {
meta.message.transition = meta.message.transition * 3.3;
Expand All @@ -1703,6 +1710,7 @@ const converters = {
},
gledopto_light_color: {
key: ['color'],
options: [exposes.options.color_sync(), exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
if (meta.message && meta.message.hasOwnProperty('transition')) {
meta.message.transition = meta.message.transition * 3.3;
Expand All @@ -1727,6 +1735,7 @@ const converters = {
},
gledopto_light_color_colortemp: {
key: ['color', 'color_temp', 'color_temp_percent'],
options: [exposes.options.color_sync(), exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
if (key == 'color') {
const result = await converters.gledopto_light_color.convertSet(entity, key, value, meta);
Expand Down Expand Up @@ -2278,6 +2287,7 @@ const converters = {
* This uses the stored state of the device to restore to the previous brightness level when turning on
*/
key: ['state', 'brightness', 'brightness_percent'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
const deviceState = meta.state || {};
const message = meta.message;
Expand Down Expand Up @@ -2928,6 +2938,7 @@ const converters = {
},
RM01_light_onoff_brightness: {
key: ['state', 'brightness', 'brightness_percent'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
if (utils.hasEndpoints(meta.device, [0x12])) {
const endpoint = meta.device.getEndpoint(0x12);
Expand All @@ -2946,6 +2957,7 @@ const converters = {
},
},
RM01_light_brightness_step: {
options: [exposes.options.transition()],
key: ['brightness_step', 'brightness_step_onoff'],
convertSet: async (entity, key, value, meta) => {
if (utils.hasEndpoints(meta.device, [0x12])) {
Expand Down Expand Up @@ -3396,6 +3408,7 @@ const converters = {
},
ptvo_switch_light_brightness: {
key: ['brightness', 'brightness_percent', 'transition'],
options: [exposes.options.transition()],
convertSet: async (entity, key, value, meta) => {
if (key === 'transition') {
return;
Expand Down Expand Up @@ -3873,9 +3886,7 @@ const converters = {
},
bticino_4027C_cover_position: {
key: ['position'],
options: [exposes.options.invert_cover(), exposes.binary('no_position_support', null, true, false)
// eslint-disable-next-line
.withDescription('Set to true when your device only reports position 0, 100 and 50 (in this case your device has an older firmware) (default false)')],
options: [exposes.options.invert_cover(), exposes.options.no_position_support()],
convertSet: async (entity, key, value, meta) => {
const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) ?
!meta.options.invert_cover : meta.options.invert_cover);
Expand Down
1 change: 1 addition & 0 deletions devices/heiman.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module.exports = [
vendor: 'HEIMAN',
fromZigbee: [fz.on_off, fz.electrical_measurement],
toZigbee: [tz.on_off],
options: [exposes.options.measurement_poll_interval()],
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']);
Expand Down
1 change: 1 addition & 0 deletions devices/tuya.js
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,7 @@ module.exports = [
endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
device.save();
},
options: [exposes.options.measurement_poll_interval()],
exposes: [e.switch(), e.power(), e.current(), e.voltage().withAccess(ea.STATE),
e.energy(), exposes.enum('power_outage_memory', ea.STATE_SET, ['on', 'off', 'restore'])
.withDescription('Recover state after power outage')],
Expand Down
13 changes: 13 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ function addDefinition(definition) {
validateDefinition(definition);
definitions.splice(0, 0, definition);

if (!definition.options) definition.options = [];
const optionKeys = definition.options.map((o) => o.name);
for (const converter of [...definition.toZigbee, ...definition.fromZigbee]) {
if (converter.options) {
for (const option of converter.options) {
if (!optionKeys.includes(option.name)) {
definition.options.push(option);
optionKeys.push(option.name);
}
}
}
}

if (definition.hasOwnProperty('fingerprint')) {
for (const fingerprint of definition.fingerprint) {
addToLookup(fingerprint.modelID, definition);
Expand Down
34 changes: 29 additions & 5 deletions lib/exposes.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ class Binary extends Base {
}
}

class List extends Base {
constructor(name, access) {
super();
this.type = 'list';
this.name = name;
this.property = name;
this.access = access;
this.item_type = 'number';
}
}

class Numeric extends Base {
constructor(name, access) {
super();
Expand Down Expand Up @@ -451,11 +462,24 @@ module.exports = {
switch: () => new Switch(),
text: (name, access) => new Text(name, access),
options: {
precision: (name) => new Numeric(`${name}_precision`).withValueMin(0).withValueMax(3).withDescription(`Number of digits after decimal point for ${name}`),
calibration: (name) => new Numeric(`${name}_calibration`).withDescription(`Calibrates the ${name} value (offset)`),
invert_cover: () => new Binary(`invert_cover`, null, true, false).withDescription(`Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false)`),
color_sync: () => new Binary(`color_sync`, null, true, false).withDescription(`When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default false)`),
thermostat_unit: () => new Enum('thermostat_unit', null, ['celsius', 'fahrenheit']).withDescription('Controls the temperature unit of the themrostat (default celsius'),
calibration: (name, type='absolute') => new Numeric(`${name}_calibration`, access.SET).withDescription(`Calibrates the ${name} value (${type} offset), takes into effect on next report of device.`),
precision: (name) => new Numeric(`${name}_precision`, access.SET).withValueMin(0).withValueMax(3).withDescription(`Number of digits after decimal point for ${name}, takes into effect on next report of device.`),
invert_cover: () => new Binary(`invert_cover`, access.SET, true, false).withDescription(`Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false).`),
color_sync: () => new Binary(`color_sync`, access.SET, true, false).withDescription(`When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).`),
thermostat_unit: () => new Enum('thermostat_unit', access.SET, ['celsius', 'fahrenheit']).withDescription('Controls the temperature unit of the themrostat (default celsius).'),
expose_pin: () => new Binary(`expose_pin`, access.SET, true, false).withDescription(`Expose pin of this lock in the published payload (default false).`),
occupancy_timeout: () => new Numeric(`occupancy_timeout`, access.SET).withValueMin(0).withDescription('Time in seconds after which occupancy is cleared after detecting it (default 90 seconds).'),
vibration_timeout: () => new Numeric(`vibration_timeout`, access.SET).withValueMin(0).withDescription('Time in seconds after which vibration is cleared after detecting it (default 90 seconds).'),
simulated_brightness: () => new Composite('simulated_brightness', 'simulated_brightness')
.withDescription('Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta.')
.withFeature(new Numeric('delta', access.SET).withValueMin(0).withDescription('Delta per interval, 20 by default'))
.withFeature(new Numeric('interval', access.SET).withValueMin(0).withUnit('ms').withDescription('Interval duration')),
no_occupancy_since: () => new List(`no_occupancy_since`, access.SET).withDescription('Sends a message the last time occupancy was detected. When setting this for example to [10, 60] a `{"no_occupancy_since": 10}` will be send after 10 seconds and a `{"no_occupancy_since": 60}` after 60 seconds.'),
presence_timeout: () => new Numeric(`presence_timeout`).withValueMin(0).withDescription('Time in seconds after which presence is cleared after detecting it (default 100 seconds).'),
no_position_support: () => new Binary('no_position_support', access.SET, true, false).withDescription('Set to true when your device only reports position 0, 100 and 50 (in this case your device has an older firmware) (default false).'),
transition: () => new Numeric(`transition`, access.SET).withValueMin(0).withDescription('Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).'),
legacy: () => new Binary(`legacy`, access.SET, true, false).withDescription(`Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).`),
measurement_poll_interval: () => new Numeric(`measurement_poll_interval`, access.SET).withValueMin(0).withDescription(`This device does not support reporting electric measurements so it is polled instead. The default poll interval is 60 seconds.`),
},
presets: {
action: (values) => new Enum('action', access.STATE, values).withDescription('Triggered action (e.g. a button click)'),
Expand Down
Loading

0 comments on commit 1159051

Please sign in to comment.