Skip to content

Commit

Permalink
fix: Rework and expand occupancy extend (#7441)
Browse files Browse the repository at this point in the history
* Update occupancy extend core

- Make standalone instead of binary derivative
- Add delay and threshold settings
- Update multi endpoint support
- Add GET support

* Fix occupancy expose access

* Remove stray import

* Add SET converters for settings

* Use numeric for settings as base
  • Loading branch information
mrskycriper committed May 1, 2024
1 parent 51b1f70 commit 0ca12a2
Showing 1 changed file with 150 additions and 25 deletions.
175 changes: 150 additions & 25 deletions src/lib/modernExtend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,41 +623,166 @@ export function soilMoisture(args?: Partial<NumericArgs>) {
});
}

export function occupancy(args?: Partial<BinaryArgs>): ModernExtend {
const name = 'occupancy';
const cluster = 'msOccupancySensing';
const attribute = 'occupancy';
const valueOn: [string | boolean, unknown] = [true, true];
const valueOff: [string | boolean, unknown] = [false, false];

const result = binary({
name: name,
cluster: cluster,
attribute: attribute,
reporting: {attribute: attribute, min: '10_SECONDS', max: '1_MINUTE', change: 0},
description: 'Indicates whether the device detected occupancy',
access: 'STATE_GET',
valueOn: valueOn,
valueOff: valueOff,
...args,
});
export interface OccupancyArgs {
pirConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[],
ultrasonicConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[],
contactConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[],
reporting?: boolean, reportingConfig?: ReportingConfigWithoutAttribute, endpointNames?: string[],
}
export function occupancy(args?: OccupancyArgs): ModernExtend {
args = {reporting: true, reportingConfig: {min: '10_SECONDS', max: '1_MINUTE', change: 0}, ...args};

const fromZigbeeOverride: Fz.Converter = {
cluster: cluster.toString(),
const templateExposes: Expose[] = [e.occupancy().withAccess(ea.STATE_GET)];
const exposes: Expose[] = args.endpointNames ?
templateExposes.map((exp) => args.endpointNames.map((ep) => exp.withEndpoint(ep))).flat() : templateExposes;

const fromZigbee: Fz.Converter[] = [{
cluster: 'msOccupancySensing',
type: ['attributeReport', 'readResponse'],
options: [opt.no_occupancy_since_false()],
convert: (model, msg, publish, options, meta) => {
if (attribute in msg.data && (!args?.endpointName || getEndpointName(msg, model, meta) === args?.endpointName)) {
const payload = {[name]: (msg.data[attribute] % 2) > 0};
noOccupancySince(msg.endpoint, options, publish, payload.occupancy ? 'stop' : 'start');
if ('occupancy' in msg.data && (!args.endpointNames || args.endpointNames.includes(getEndpointName(msg, model, meta).toString()))) {
const propertyName = postfixWithEndpointName('occupancy', msg, model, meta);
const payload = {[propertyName]: (msg.data['occupancy'] & 1) > 0};
noOccupancySince(msg.endpoint, options, publish, payload[propertyName] ? 'stop' : 'start');
return payload;
}
},
}];

const toZigbee: Tz.Converter[] = [{
key: ['occupancy'],
convertGet: async (entity, key, meta) => {
await entity.read('msOccupancySensing', ['occupancy']);
},
}];

const settingsExtends: ModernExtend[] = [];

const settingsTemplate = {
cluster: 'msOccupancySensing',
description: '',
endpointNames: args.endpointNames,
access: 'ALL' as 'STATE' | 'STATE_GET' | 'ALL',
entityCategory: 'config' as 'config' | 'diagnostic',
};

result.fromZigbee[0] = fromZigbeeOverride;
const attributesForReading: string[] = [];

if (args.pirConfig) {
if (args.pirConfig.includes('otu_delay')) {
settingsExtends.push(numeric({
name: 'pir_otu_delay',
attribute: 'pirOToUDelay',
valueMin: 0,
valueMax: 65534,
...settingsTemplate,
}));
attributesForReading.push('pirOToUDelay');
}
if (args.pirConfig.includes('uto_delay')) {
settingsExtends.push(numeric({
name: 'pir_uto_delay',
attribute: 'pirUToODelay',
valueMin: 0,
valueMax: 65534,
...settingsTemplate,
}));
attributesForReading.push('pirUToODelay');
}
if (args.pirConfig.includes('uto_threshold')) {
settingsExtends.push(numeric({
name: 'pir_uto_threshold',
attribute: 'pirUToOThreshold',
valueMin: 1,
valueMax: 254,
...settingsTemplate,
}));
attributesForReading.push('pirUToOThreshold');
}
}

return result;
if (args.ultrasonicConfig) {
if (args.pirConfig.includes('otu_delay')) {
settingsExtends.push(numeric({
name: 'ultrasonic_otu_delay',
attribute: 'ultrasonicOToUDelay',
valueMin: 0,
valueMax: 65534,
...settingsTemplate,
}));
attributesForReading.push('ultrasonicOToUDelay');
}
if (args.pirConfig.includes('uto_delay')) {
settingsExtends.push(numeric({
name: 'ultrasonic_uto_delay',
attribute: 'ultrasonicUToODelay',
valueMin: 0,
valueMax: 65534,
...settingsTemplate,
}));
attributesForReading.push('ultrasonicUToODelay');
}
if (args.pirConfig.includes('uto_threshold')) {
settingsExtends.push(numeric({
name: 'ultrasonic_uto_threshold',
attribute: 'ultrasonicUToOThreshold',
valueMin: 1,
valueMax: 254,
...settingsTemplate,
}));
attributesForReading.push('ultrasonicUToOThreshold');
}
}

if (args.contactConfig) {
if (args.pirConfig.includes('otu_delay')) {
settingsExtends.push(numeric({
name: 'contact_otu_delay',
attribute: 'contactOToUDelay',
valueMin: 0,
valueMax: 65534,
...settingsTemplate,
}));
attributesForReading.push('contactOToUDelay');
}
if (args.pirConfig.includes('uto_delay')) {
settingsExtends.push(numeric({
name: 'contact_uto_delay',
attribute: 'contactUToODelay',
valueMin: 0,
valueMax: 65534,
...settingsTemplate,
}));
attributesForReading.push('contactUToODelay');
}
if (args.pirConfig.includes('uto_threshold')) {
settingsExtends.push(numeric({
name: 'contact_uto_threshold',
attribute: 'contactUToOThreshold',
valueMin: 1,
valueMax: 254,
...settingsTemplate,
}));
attributesForReading.push('contactUToOThreshold');
}
}

settingsExtends.map((extend) => exposes.push(...extend.exposes));
settingsExtends.map((extend) => fromZigbee.push(...extend.fromZigbee));
settingsExtends.map((extend) => toZigbee.push(...extend.toZigbee));

const configure: Configure[] = [];

if (attributesForReading.length > 0) configure.push(setupConfigureForReading('msOccupancySensing', attributesForReading, args.endpointNames));

if (args.reporting) {
configure.push(
setupConfigureForReporting('msOccupancySensing', 'occupancy', args.reportingConfig, ea.STATE_GET, args.endpointNames),
);
}

return {exposes, fromZigbee, toZigbee, configure, isModernExtend: true};
}

export function co2(args?: Partial<NumericArgs>) {
Expand Down

0 comments on commit 0ca12a2

Please sign in to comment.