Skip to content

Commit

Permalink
feat(add): ZigUSB_C6 (#8120)
Browse files Browse the repository at this point in the history
* Add ZigUSB_C6 support

* add support for DC in the electricityMeter modernExtend

* Fixed power_on_behavior in the onOff extend when using multiple endpoints and added support for descriptions.
Added support for descriptions in the iasZoneAlarm extend.

* A lot of small fixes suggested by @Koenkk

* ElectricityMeterArgs fix

* integrate-pr-8123

* Updates

* Updates

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
  • Loading branch information
xyzroe and Koenkk authored Oct 17, 2024
1 parent ebd3b74 commit ba5a32d
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 9 deletions.
9 changes: 8 additions & 1 deletion src/converters/toZigbee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,14 @@ const converters2 = {
utils.assertString(value, key);
value = value.toLowerCase();
const lookup = {off: 0, on: 1, toggle: 2, previous: 255};
await entity.write('genOnOff', {startUpOnOff: utils.getFromLookup(value, lookup)}, utils.getOptions(meta.mapped, entity));
try {
await entity.write('genOnOff', {startUpOnOff: utils.getFromLookup(value, lookup)}, utils.getOptions(meta.mapped, entity));
} catch (e) {
if (e.message.includes('UNSUPPORTED_ATTRIBUTE')) {
throw new Error('Got `UNSUPPORTED_ATTRIBUTE` error, device does not support power on behaviour');
}
throw e;
}
return {state: {power_on_behavior: value}};
},
convertGet: async (entity, key, meta) => {
Expand Down
26 changes: 26 additions & 0 deletions src/devices/xyzroe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fz from '../converters/fromZigbee';
import tz from '../converters/toZigbee';
import * as exposes from '../lib/exposes';
import * as legacy from '../lib/legacy';
import {deviceEndpoints, electricityMeter, iasZoneAlarm, identify, onOff, temperature} from '../lib/modernExtend';
import {DefinitionWithExtend, Fz, KeyValueAny, Tz} from '../lib/types';
import * as utils from '../lib/utils';

Expand Down Expand Up @@ -463,6 +464,31 @@ const definitions: DefinitionWithExtend[] = [
]);
},
},
{
zigbeeModel: ['ZigUSB_C6'],
model: 'ZigUSB_C6',
vendor: 'xyzroe',
description: 'Zigbee USB switch with monitoring',
extend: [
deviceEndpoints({endpoints: {1: 1, 2: 2, 3: 3}}),
identify(),
electricityMeter({
cluster: 'electrical',
electricalMeasurementType: 'both',
// Since this device measures lower voltage devices, lower the change value.
current: {change: 100},
power: {change: 100},
voltage: {change: 100},
endpointNames: ['1'],
}),
temperature(),
onOff({endpointNames: ['1'], description: 'Controls the USB port'}),
onOff({powerOnBehavior: false, endpointNames: ['2'], description: 'Indicates the Zigbee status'}),
onOff({powerOnBehavior: false, endpointNames: ['3'], description: 'Indicates the USB state'}),
iasZoneAlarm({zoneType: 'generic', zoneAttributes: ['alarm_1'], description: 'Over current alarm'}),
],
meta: {multiEndpoint: true},
},
];

export default definitions;
Expand Down
6 changes: 2 additions & 4 deletions src/lib/exposes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1198,9 +1198,7 @@ export const presets = {
power_on_behavior: (values = ['off', 'previous', 'on']) =>
new Enum('power_on_behavior', access.ALL, values)
.withLabel('Power-on behavior')
.withDescription(
'Controls the behavior when the device is powered on after power loss. If you get an `UNSUPPORTED_ATTRIBUTE` error, the device does not support it.',
)
.withDescription('Controls the behavior when the device is powered on after power loss')
.withCategory('config'),
power_outage_count: (resetsWhenPairing = true) =>
new Numeric('power_outage_count', access.STATE)
Expand Down Expand Up @@ -1240,7 +1238,7 @@ export const presets = {
sos: () => new Binary('sos', access.STATE, true, false).withLabel('SOS').withDescription('SOS alarm'),
sound_volume: () =>
new Enum('sound_volume', access.ALL, ['silent_mode', 'low_volume', 'high_volume']).withDescription('Sound volume of the lock'),
switch: () => new Switch().withState('state', true, 'On/off state of the switch'),
switch: (description: string = 'On/off state of the switch') => new Switch().withState('state', true, description),
switch_type: () => new Enum('switch_type', access.ALL, ['toggle', 'momentary']).withDescription('Wall switch type'),
door_state: () =>
new Enum('door_state', access.STATE, [
Expand Down
60 changes: 56 additions & 4 deletions src/lib/modernExtend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,17 +466,20 @@ export interface OnOffArgs {
skipDuplicateTransaction?: boolean;
endpointNames?: string[];
configureReporting?: boolean;
description?: string;
}
export function onOff(args?: OnOffArgs): ModernExtend {
args = {powerOnBehavior: true, skipDuplicateTransaction: false, configureReporting: true, ...args};

const exposes: Expose[] = exposeEndpoints(e.switch(), args.endpointNames);
const exposes: Expose[] = args.description
? exposeEndpoints(e.switch(args.description), args.endpointNames)
: exposeEndpoints(e.switch(), args.endpointNames);

const fromZigbee: Fz.Converter[] = [args.skipDuplicateTransaction ? fz.on_off_skip_duplicate_transaction : fz.on_off];
const toZigbee: Tz.Converter[] = [tz.on_off];

if (args.powerOnBehavior) {
exposes.push(e.power_on_behavior(['off', 'on', 'toggle', 'previous']));
exposes.push(...exposeEndpoints(e.power_on_behavior(['off', 'on', 'toggle', 'previous']), args.endpointNames));
fromZigbee.push(fz.power_on_behavior);
toZigbee.push(tz.power_on_behavior);
}
Expand Down Expand Up @@ -1394,6 +1397,7 @@ export interface IasArgs {
zoneType: iasZoneType;
zoneAttributes: iasZoneAttribute[];
alarmTimeout?: boolean;
description?: string;
}
export function iasZoneAlarm(args: IasArgs): ModernExtend {
const exposeList = {
Expand Down Expand Up @@ -1448,7 +1452,13 @@ export function iasZoneAlarm(args: IasArgs): ModernExtend {
let alarm2Name = 'alarm_2';

if (args.zoneType === 'generic') {
args.zoneAttributes.map((attr) => exposes.push(exposeList[attr]));
args.zoneAttributes.map((attr) => {
let expose = exposeList[attr];
if (args.description) {
expose = expose.clone().withDescription(args.description);
}
exposes.push(expose);
});
} else {
if (bothAlarms) {
exposes.push(
Expand Down Expand Up @@ -1611,6 +1621,7 @@ export function iasWarning(args?: IasWarningArgs): ModernExtend {
type MultiplierDivisor = {multiplier?: number; divisor?: number};
export interface ElectricityMeterArgs {
cluster?: 'both' | 'metering' | 'electrical';
electricalMeasurementType?: 'both' | 'ac' | 'dc';
current?: false | (MultiplierDivisor & Partial<ReportingConfigWithoutAttribute>);
power?: false | (MultiplierDivisor & Partial<ReportingConfigWithoutAttribute>);
voltage?: false | (MultiplierDivisor & Partial<ReportingConfigWithoutAttribute>);
Expand All @@ -1625,7 +1636,16 @@ export interface ElectricityMeterArgs {
fzElectricalMeasurement?: Fz.Converter;
}
export function electricityMeter(args?: ElectricityMeterArgs): ModernExtend {
args = {cluster: 'both', configureReporting: true, threePhase: false, producedEnergy: false, acFrequency: false, powerFactor: false, ...args};
args = {
cluster: 'both',
electricalMeasurementType: 'ac',
configureReporting: true,
threePhase: false,
producedEnergy: false,
acFrequency: false,
powerFactor: false,
...args,
};
if (args.cluster !== 'electrical') {
const divisors = new Set([
args.cluster === 'metering' && isObject(args.power) ? args.power?.divisor : false,
Expand Down Expand Up @@ -1655,6 +1675,9 @@ export function electricityMeter(args?: ElectricityMeterArgs): ModernExtend {
if (args.cluster === 'metering' && args.powerFactor) {
throw new Error(`Power factor is not supported with cluster 'metering', use 'both' or 'electrical'`);
}
if (args.cluster === 'metering' && args.electricalMeasurementType === 'dc') {
throw new Error(`DC attributes are not supported with cluster 'metering', use 'both' or 'ac'`);
}

let exposes: Numeric[] = [];
let fromZigbee: Fz.Converter[];
Expand Down Expand Up @@ -1710,6 +1733,12 @@ export function electricityMeter(args?: ElectricityMeterArgs): ModernExtend {
forced: args.voltage,
change: 5,
},
// Report change with every 100mW change
dc_power: {attribute: 'dcPower', divisor: 'dcPowerDivisor', multiplier: 'dcPowerMultiplier', forced: args.power, change: 100},
// Report change with every 100mV change
dc_voltage: {attribute: 'dcVoltage', divisor: 'dcVoltageDivisor', multiplier: 'dcVoltageMultiplier', forced: args.voltage, change: 100},
// Report change with every 100mA change
dc_current: {attribute: 'dcCurrent', divisor: 'dcCurrentDivisor', multiplier: 'dcCurrentMultiplier', forced: args.current, change: 100},
},
seMetering: {
// Report change with every 5W change
Expand All @@ -1731,16 +1760,19 @@ export function electricityMeter(args?: ElectricityMeterArgs): ModernExtend {
delete configureLookup.seMetering.power;
delete configureLookup.haElectricalMeasurement.power_phase_b;
delete configureLookup.haElectricalMeasurement.power_phase_c;
delete configureLookup.haElectricalMeasurement.dc_power;
}
if (args.voltage === false) {
delete configureLookup.haElectricalMeasurement.voltage;
delete configureLookup.haElectricalMeasurement.voltage_phase_b;
delete configureLookup.haElectricalMeasurement.voltage_phase_c;
delete configureLookup.haElectricalMeasurement.dc_voltage;
}
if (args.current === false) {
delete configureLookup.haElectricalMeasurement.current;
delete configureLookup.haElectricalMeasurement.current_phase_b;
delete configureLookup.haElectricalMeasurement.current_phase_c;
delete configureLookup.haElectricalMeasurement.dc_current;
}
if (args.energy === false) {
delete configureLookup.seMetering.energy;
Expand All @@ -1763,6 +1795,26 @@ export function electricityMeter(args?: ElectricityMeterArgs): ModernExtend {
delete configureLookup.haElectricalMeasurement.voltage_phase_c;
}

if (args.electricalMeasurementType === 'dc') {
delete configureLookup.haElectricalMeasurement.power;
delete configureLookup.haElectricalMeasurement.voltage;
delete configureLookup.haElectricalMeasurement.current;
delete configureLookup.haElectricalMeasurement.power_factor;
delete configureLookup.haElectricalMeasurement.ac_frequency;
delete configureLookup.haElectricalMeasurement.power_phase_b;
delete configureLookup.haElectricalMeasurement.power_phase_c;
delete configureLookup.haElectricalMeasurement.current_phase_b;
delete configureLookup.haElectricalMeasurement.current_phase_c;
delete configureLookup.haElectricalMeasurement.voltage_phase_b;
delete configureLookup.haElectricalMeasurement.voltage_phase_c;
}

if (args.electricalMeasurementType === 'ac') {
delete configureLookup.haElectricalMeasurement.dc_power;
delete configureLookup.haElectricalMeasurement.dc_voltage;
delete configureLookup.haElectricalMeasurement.dc_current;
}

if (args.cluster === 'both') {
if (args.power !== false) exposes.push(e.power().withAccess(ea.STATE_GET));
if (args.voltage !== false) exposes.push(e.voltage().withAccess(ea.STATE_GET));
Expand Down

0 comments on commit ba5a32d

Please sign in to comment.