Skip to content

Commit 25a2a79

Browse files
ptvoinfoptvoinfo
andauthored
fix: Various fixes for ptvo.switch (#6033)
* Fixes related to ptvoinfo/zigbee-configurable-firmware#208 * Formatting fixes. * Formatting fixed --------- Co-authored-by: ptvoinfo <[email protected]>
1 parent 5723131 commit 25a2a79

File tree

1 file changed

+111
-26
lines changed

1 file changed

+111
-26
lines changed

src/devices/custom_devices_diy.ts

Lines changed: 111 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ const definitions: Definition[] = [
507507
zigbeeModel: ['ptvo.switch'],
508508
model: 'ptvo.switch',
509509
vendor: 'Custom devices (DiY)',
510-
description: '[Multi-channel relay switch](https://ptvo.info/zigbee-switch-configurable-firmware-router-199/)',
510+
description: '[Multi-functional device](https://ptvo.info/zigbee-configurable-firmware-features/)',
511511
fromZigbee: [fz.on_off, fz.ptvo_multistate_action, legacy.fz.ptvo_switch_buttons, fz.ptvo_switch_uart,
512512
fz.ptvo_switch_analog_input, fz.brightness, fz.ignore_basic_report, fz.temperature,
513513
fzLocal.humidity2, fzLocal.pressure2, fzLocal.illuminance2],
@@ -516,9 +516,8 @@ const definitions: Definition[] = [
516516
const expose: Expose[] = [];
517517
const exposeDeviceOptions: KeyValue = {};
518518
const deviceConfig = ptvoGetMetaOption(device, 'device_config', '');
519-
520519
if (deviceConfig === '') {
521-
if ( (device != null) && device.endpoints ) {
520+
if ((device != null) && device.endpoints) {
522521
for (const endpoint of device.endpoints) {
523522
const exposeEpOptions: KeyValue = {};
524523
ptvoAddStandardExposes(endpoint, expose, exposeEpOptions, exposeDeviceOptions);
@@ -533,39 +532,119 @@ const definitions: Definition[] = [
533532
}
534533
}
535534
} else {
536-
for (let i = 0; i < deviceConfig.length; i++) {
537-
const epConfig = deviceConfig.charCodeAt(i);
538-
if (epConfig <= 0x20) {
535+
// device configuration description from a device
536+
const deviceConfigArray = deviceConfig.split(/[\r\n]+/);
537+
let prevEp = -1;
538+
for (let i = 0; i < deviceConfigArray.length; i++) {
539+
const epConfig = deviceConfigArray[i];
540+
const epId = parseInt(epConfig.substr(0, 1), 16);
541+
if (epId <= 0) {
539542
continue;
540543
}
541-
const epId = i + 1;
542544
const epName = `l${epId}`;
545+
const epValueAccessRights = epConfig.substr(1, 1);
546+
const epStateType = ((epValueAccessRights === 'W') || (epValueAccessRights === '*'))?
547+
ea.STATE_SET: ea.STATE;
548+
let valueConfig = epConfig.substr(2);
549+
valueConfig = valueConfig.split(',');
550+
let valueId = (valueConfig[0])? valueConfig[0]: '';
551+
let valueDescription = (valueConfig[1])? valueConfig[1]: '';
552+
let valueUnit = (valueConfig[2] !== undefined)? valueConfig[2]: '';
543553
const exposeEpOptions: KeyValue = {};
544-
if ((epConfig & 0x01) != 0) {
545-
// GPIO input
546-
exposeEpOptions['expose_action'] = true;
547-
}
548-
if ((epConfig & 0x02) != 0) {
549-
// GPIO output
554+
if (valueId === '*') {
555+
// GPIO output (Generic)
550556
exposeEpOptions['exposed_onoff'] = true;
551557
expose.push(e.switch().withEndpoint(epName));
552-
}
553-
if ((epConfig & 0x04) != 0) {
554-
// reportable analog value
555-
exposeEpOptions['exposed_analog'] = true;
556-
expose.push(e.numeric(epName, ea.STATE).withDescription('State or sensor value'));
557-
} else if ((epConfig & 0x08) != 0) {
558-
// readable analog value
558+
} else if (valueId === '#') {
559+
// GPIO state (contact, gas, noise, occupancy, presence, smoke, sos, tamper, vibration, water leak)
560+
exposeEpOptions['exposed_onoff'] = true;
561+
let exposeObj = undefined;
562+
switch (valueDescription) {
563+
case 'g': exposeObj = e.gas(); break;
564+
case 'n': exposeObj = e.noise_detected(); break;
565+
case 'o': exposeObj = e.occupancy(); break;
566+
case 'p': exposeObj = e.presence(); break;
567+
case 'm': exposeObj = e.smoke(); break;
568+
case 's': exposeObj = e.sos(); break;
569+
case 't': exposeObj = e.tamper(); break;
570+
case 'v': exposeObj = e.vibration(); break;
571+
case 'w': exposeObj = e.water_leak(); break;
572+
default: // 'c'
573+
exposeObj = e.contact();
574+
}
575+
expose.push(exposeObj.withEndpoint(epName));
576+
} else if (valueConfig) {
577+
let valueName = undefined; // name in Z2M
578+
let valueNumIndex = undefined;
579+
const idxPos = valueId.search(/(\d+)$/);
580+
if (valueId.startsWith('mcpm') || valueId.startsWith('ncpm')) {
581+
const num = parseInt(valueId.substr(4, 1), 16);
582+
valueName = valueId.substr(0, 4) + num;
583+
} else if (idxPos >= 0) {
584+
valueNumIndex = valueId.substr(idxPos);
585+
valueId = valueId.substr(0, idxPos);
586+
}
587+
588+
// analog value
589+
// 1: value name (if empty, use the EP name)
590+
// 2: description (if empty or undefined, use the value name)
591+
// 3: units (if undefined, use the key name)
592+
const infoLookup: KeyValue = {
593+
'C': 'temperature',
594+
'%': 'humidity',
595+
'm': 'altitude',
596+
'Pa': 'pressure',
597+
'ppm': 'quality',
598+
'psize': 'particle_size',
599+
'V': 'voltage',
600+
'A': 'current',
601+
'Wh': 'energy',
602+
'W': 'power',
603+
'Hz': 'frequency',
604+
'pf': 'power_factor',
605+
'lx': 'illuminance_lux',
606+
};
607+
valueName = (valueName !== undefined)? valueName: infoLookup[valueId];
608+
609+
if ((valueName === undefined) && valueNumIndex) {
610+
valueName = 'val' + valueNumIndex;
611+
}
612+
613+
valueName = (valueName === undefined)? epName: valueName + '_' + epName;
614+
615+
if ((valueDescription === undefined) || (valueDescription === '')) {
616+
if (infoLookup[valueId]) {
617+
valueDescription = infoLookup[valueId];
618+
valueDescription = valueDescription.replace('_', ' ');
619+
} else {
620+
valueDescription = 'Sensor value';
621+
}
622+
}
623+
valueDescription = valueDescription.substring(0, 1).toUpperCase() +
624+
valueDescription.substring(1);
625+
626+
if (valueNumIndex) {
627+
valueDescription = valueDescription + ' ' + valueNumIndex;
628+
}
629+
630+
if (((valueUnit === undefined) || (valueUnit === '')) && infoLookup[valueId]) {
631+
valueUnit = valueId;
632+
}
633+
559634
exposeEpOptions['exposed_analog'] = true;
560-
expose.push(e.numeric(epName, ea.STATE_SET)
635+
expose.push(e.numeric(valueName, epStateType)
561636
.withValueMin(-9999999).withValueMax(9999999).withValueStep(1)
562-
.withDescription('State or sensor value'));
637+
.withDescription(valueDescription)
638+
.withUnit(valueUnit));
563639
}
564640
const endpoint = device.getEndpoint(epId);
565641
if (!endpoint) {
566642
continue;
567643
}
568-
ptvoAddStandardExposes(endpoint, expose, exposeEpOptions, exposeDeviceOptions);
644+
if (prevEp !== epId) {
645+
prevEp = epId;
646+
ptvoAddStandardExposes(endpoint, expose, exposeEpOptions, exposeDeviceOptions);
647+
}
569648
}
570649
}
571650
if (exposeDeviceOptions['expose_action']) {
@@ -583,7 +662,7 @@ const definitions: Definition[] = [
583662
const endpointList: any = [];
584663
const deviceConfig = ptvoGetMetaOption(device, 'device_config', '');
585664
if (deviceConfig === '') {
586-
if ( (device != null) && device.endpoints ) {
665+
if ((device != null) && device.endpoints) {
587666
for (const endpoint of device.endpoints) {
588667
const epId = endpoint.ID;
589668
const epName = `l${epId}`;
@@ -614,8 +693,14 @@ const definitions: Definition[] = [
614693
if (device != null) {
615694
const controlEp = device.getEndpoint(1);
616695
if (controlEp != null) {
617-
ptvoSetMetaOption(device, 'device_config', (await controlEp.read('genBasic', ['locationDesc'])).locationDesc);
618-
device.save();
696+
try {
697+
let deviceConfig = await controlEp.read('genBasic', [32768]);
698+
if (deviceConfig) {
699+
deviceConfig = deviceConfig['32768'];
700+
ptvoSetMetaOption(device, 'device_config', deviceConfig);
701+
device.save();
702+
}
703+
} catch (err) {/* do nothing */}
619704
}
620705
}
621706
},

0 commit comments

Comments
 (0)