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

Exposing action_brightness_delta to allow better blueprints #8369

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
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
20 changes: 20 additions & 0 deletions src/converters/fromZigbee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,7 @@ const converters1 = {
command_step_color_temperature: {
cluster: 'lightingColorCtrl',
type: 'commandStepColorTemp',
options: [exposes.options.simulated_color_temperature()],
convert: (model, msg, publish, options, meta) => {
if (hasAlreadyProcessedMessage(msg, model)) return;
const direction = msg.data.stepmode === 1 ? 'up' : 'down';
Expand All @@ -1413,6 +1414,25 @@ const converters1 = {
payload.action_transition_time = msg.data.transtime / 100;
}

if (options.simulated_color_temperature) {
const SIMULATED_COLOR_TEMPERATURE_KEY = 'simulated_color_temperature_temperature';

const opts: KeyValueAny = options.simulated_color_temperature;
const deltaOpts: number = typeof opts === 'object' && opts.delta !== undefined ? opts.delta : 50;
const minimumOpts: number = typeof opts === 'object' && opts.minimum !== undefined ? opts.minimum : 153;
const maximumOpts: number = typeof opts === 'object' && opts.maximum !== undefined ? opts.maximum : 500;

let colorTemperature = globalStore.getValue(msg.endpoint, SIMULATED_COLOR_TEMPERATURE_KEY, 250);
const delta = direction === 'up' ? deltaOpts : -deltaOpts;
colorTemperature += delta;
colorTemperature = numberWithinRange(colorTemperature, minimumOpts, maximumOpts);
globalStore.putValue(msg.endpoint, SIMULATED_COLOR_TEMPERATURE_KEY, colorTemperature);
const property = postfixWithEndpointName('color_temperature', msg, model, meta);
payload[property] = colorTemperature;
const deltaProperty = postfixWithEndpointName('action_color_temperature_delta', msg, model, meta);
payload[deltaProperty] = delta;
}

addActionGroup(payload, msg, model);
return payload;
},
Expand Down
4 changes: 2 additions & 2 deletions src/devices/lutron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as reporting from '../lib/reporting';
import {DefinitionWithExtend} from '../lib/types';

const e = exposes.presets;
const ea = exposes.access;

const definitions: DefinitionWithExtend[] = [
{
Expand All @@ -23,8 +22,9 @@ const definitions: DefinitionWithExtend[] = [
vendor: 'Lutron',
description: 'Aurora smart bulb dimmer',
fromZigbee: [fz.command_move_to_level],
toZigbee: [],
extend: [battery()],
exposes: [e.action(['brightness']), e.numeric('brightness', ea.STATE)],
exposes: [e.action(['brightness'])],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ['genLevelCtrl']);
Expand Down
5 changes: 0 additions & 5 deletions src/devices/philips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3192,11 +3192,6 @@ const definitions: DefinitionWithExtend[] = [
.withDescription('value in seconds representing the amount of time the last action took')
.withValueMin(0)
.withValueMax(255),
e
.numeric('brightness', ea.STATE)
.withDescription('Raw rotation state value of the dial which represents brightness from 0-255')
.withValueMin(0)
.withValueMax(255),
e
.numeric('action_step_size', ea.STATE)
.withDescription('amount of steps the last action took on the dial exposed as a posive value from 0-255')
Expand Down
55 changes: 53 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,18 @@ function processExtensions(definition: DefinitionWithExtend): Definition {
return definition;
}

function additionalExposesFromOptions(definition: DefinitionWithExtend): Expose[] {
const additionalExposes: Expose[] = [];
for (const option of definition.options) {
if (option.features?.find((e) => e.exposed)?.exposed) {
additionalExposes.push(...option.features.filter((e) => e.exposed));
}
}
return additionalExposes;
}

function prepareDefinition(definition: DefinitionWithExtend): Definition {
definition = processExtensions(definition);

definition.toZigbee.push(
toZigbee.scene_store,
toZigbee.scene_recall,
Expand All @@ -336,7 +345,6 @@ function prepareDefinition(definition: DefinitionWithExtend): Definition {
if (!definition.options) {
definition.options = [];
}

const optionKeys = definition.options.map((o) => o.name);

// Add calibration/precision options based on expose
Expand Down Expand Up @@ -376,6 +384,48 @@ function prepareDefinition(definition: DefinitionWithExtend): Definition {
}
}

//if options are defined, add them to the exposes
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I correct, that you're implying that it better should be done by the modernExtendRefactor script, to migrate all definitions to extend a simulated_light base? That sounds to be a great approach, but I'm a bit unsure if it could handle the edge cases like the D1 universal dimmer, that's an example of why this got this complicated, because it acts as a dimmer, but also as a light entity.

Is this what you're intending to push forward, or am I on the wrong track? If not I'll read into the code base and check how to integrate it

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant the changes of src/lib/modernExtend.ts

if (definition.exposes && Array.isArray(definition.exposes) && definition.options) {
const existingExposes = definition.exposes
.flatMap((e) => {
return [e, ...(e.features ?? [])];
})
.reduce(
(acc, expose) => {
acc[expose.name] = expose;
return acc;
},
{} as Record<string, Expose>,
);
const existingExposesNames = Object.keys(existingExposes);

const additionalExposes = additionalExposesFromOptions(definition);

const newExposes = additionalExposes.map((additionalExpose) => {
if (!existingExposesNames.includes(additionalExpose.name)) {
return additionalExpose;
}

const existingExpose = existingExposes[additionalExpose.name];
const converter = definition.toZigbee.find(
(item) =>
item.key.includes(additionalExpose.property) &&
(!item.endpoints || (additionalExpose.endpoint && item.endpoints.includes(additionalExpose.endpoint))),
);
// If the converter does not support SET or GET, remove the access afterwards
let access = additionalExpose.access | existingExpose.access;
if (!converter?.convertSet) {
access &= ~exposesLib.access.SET;
}
if (!converter?.convertGet) {
access &= ~exposesLib.access.GET;
}
return {...additionalExpose, access: access} as Expose;
});

definition.exposes = definition.exposes.concat(newExposes);
}

return definition;
}

Expand Down Expand Up @@ -486,6 +536,7 @@ export async function findDefinition(device: Zh.Device, generateForUnknown: bool
return candidates[0];
} else {
// First try to match based on fingerprint, return the first matching one.

const fingerprintMatch: {priority?: number; definition?: Definition} = {priority: undefined, definition: undefined};

for (const candidate of candidates) {
Expand Down
47 changes: 46 additions & 1 deletion src/lib/exposes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class Base {
description?: string;
features?: Feature[];
category?: 'config' | 'diagnostic';
exposed?: boolean;

withEndpoint(endpointName: string) {
this.endpoint = endpointName;
Expand All @@ -35,6 +36,11 @@ export class Base {
return this;
}

setExposed() {
this.exposed = true;
return this;
}

withAccess(a: number) {
assert(this.access !== undefined, 'Cannot add access if not defined yet');
this.access = a;
Expand Down Expand Up @@ -824,7 +830,46 @@ export const options = {
`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. The action_brightness_delta indicates the delta for each interval.`,
)
.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')),
.withFeature(new Numeric('interval', access.SET).withValueMin(0).withUnit('ms').withDescription('Interval duration'))
.withFeature(
new Numeric('brightness', access.STATE)
.withValueMin(0)
.withValueMax(255)
.withDescription('Current simulated brightness value.')
.setExposed(),
)
.withFeature(new Numeric('action_brightness_delta', access.STATE).withDescription('Action step size for brightness.').setExposed()),
simulated_color_temperature: () =>
new Composite('Simulated Color Temperature', 'simulated_color_temperature', access.SET)
.withFeature(
new Numeric('delta', access.SET)
.withValueMin(1)
.withDescription('Delta describes how much per tick the color temperature will change.'),
)
.withFeature(
new Numeric('minimum', access.SET)
.withValueMin(0)
.withValueMax(1000)
.withDescription('Minimum midred value that will be simulated. Default 153 midred (~6500 kelvin)'),
)
.withFeature(
new Numeric('maximum', access.SET)
.withValueMin(0)
.withValueMax(1000)
.withDescription('Maximum midred value that will be simulated. Default 500 midred (~2000 kelvin)'),
)
.withFeature(
new Numeric('color_temperature', access.STATE).withUnit('midred').withDescription('Current simulated midred value.').setExposed(),
)
.withFeature(
new Numeric('action_color_temperature_delta', access.STATE)
.withUnit('midred')
.withValueMin(153)
.withValueMax(500)
.withDescription('Action step size for color temperature.')
.setExposed(),
)
.withDescription('Simulate a color temperature in midred with minimum and maximum value and a delta value '),
no_occupancy_since_true: () =>
new List(`no_occupancy_since`, access.SET, new Numeric('time', access.STATE_SET)).withDescription(
'Sends a message the last time occupancy (occupancy: true) 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.',
Expand Down
2 changes: 2 additions & 0 deletions test/modernExtend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ describe('ModernExtend', () => {
],
exposes: [
'action',
'action_brightness_delta',
'brightness',
'effect',
'effect',
'effect',
Expand Down
Loading