From edcbee890b59627af0e48ae335328147da1f74e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Sat, 28 Sep 2024 22:14:05 +0200 Subject: [PATCH 1/9] Update frient powermeter led 2 --- src/devices/frient.ts | 114 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index eda40a3a510f6..4c8bf5ad24d3b 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -1,24 +1,115 @@ +import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as exposes from '../lib/exposes'; import {electricityMeter, onOff, ota} from '../lib/modernExtend'; -import * as reporting from '../lib/reporting'; -import {DefinitionWithExtend} from '../lib/types'; +import {DefinitionWithExtend, Fz, Tz} from '../lib/types'; +import * as reporting from '../lib/reporting' +import * as constants from '../lib/constants' +import * as utils from '../lib/utils' const e = exposes.presets; +const ea = exposes.access; + +// develco specific cosntants +const manufacturerOptions = {manufacturerCode: 0x1015}; + + +// develco specific convertors +const frient = { + fz: { + pulse_configuration: { + cluster: 'seMetering', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result = {}; + if (msg.data.hasOwnProperty('develcoPulseConfiguration')) { + result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = + msg.data['develcoPulseConfiguration']; + } + + return result; + }, + } satisfies Fz.Converter, + interface_mode: { + cluster: 'seMetering', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result = {}; + if (msg.data.hasOwnProperty('develcoInterfaceMode')) { + result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = + constants.develcoInterfaceMode.hasOwnProperty(msg.data['develcoInterfaceMode']) ? + constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] : + msg.data['develcoInterfaceMode']; + } + if (msg.data.hasOwnProperty('status')) { + result['battery_low'] = (msg.data.status & 2) > 0; + result['check_meter'] = (msg.data.status & 1) > 0; + } + + return result; + }, + } satisfies Fz.Converter, + }, + tz: { + pulse_configuration: { + key: ['pulse_configuration'], + convertSet: async (entity, key, value, meta) => { + await entity.write('seMetering', {'develcoPulseConfiguration': value}, manufacturerOptions); + return {readAfterWriteTime: 200, state: {'pulse_configuration': value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); + }, + } satisfies Tz.Converter, + interface_mode: { + key: ['interface_mode'], + convertSet: async (entity, key, value, meta) => { + const payload = {'develcoInterfaceMode': utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)}; + await entity.write('seMetering', payload, manufacturerOptions); + return {readAfterWriteTime: 200, state: {'interface_mode': value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions); + }, + } satisfies Tz.Converter, + current_summation: { + key: ['current_summation'], + convertSet: async (entity, key, value, meta) => { + await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions); + return {state: {'current_summation': value}}; + }, + } satisfies Tz.Converter, + }, +}; const definitions: DefinitionWithExtend[] = [ { - zigbeeModel: ['EMIZB-141'], - model: 'EMIZB-141', - vendor: 'Frient', - description: 'Smart powermeter Zigbee bridge', - fromZigbee: [fz.metering, fz.battery], - toZigbee: [], + zigbeeModel: ['EMIZB-141'], // The model ID from: Device with modelID 'lumi.sens' is not supported. + model: 'EMIZB-141', // Vendor model number, look on the device for a model number + vendor: 'Frient A/S', // Vendor of the device (only used for documentation and startup logging) + description: 'frient Electricity Meter Interface 2 LED', // Description of the device, copy from vendor site. (only used for documentation and startup logging) + fromZigbee: [fz.metering, fz.battery, frient.fz.pulse_configuration, frient.fz.interface_mode], + toZigbee: [frient.tz.pulse_configuration, frient.tz.interface_mode, frient.tz.current_summation], extend: [ota()], - exposes: [e.battery(), e.power(), e.energy()], - configure: async (device, coordinatorEndpoint) => { + exposes: [ + e.power(), + e.energy(), + e.battery_low(), + e.numeric('pulse_configuration', ea.ALL).withValueMin(0).withValueMax(65535) + .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), + e.enum('interface_mode', ea.ALL, + ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0']) + .withDescription('Operating mode/probe'), + e.numeric('current_summation', ea.SET) + .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh').withValueMin(0) + .withValueMax(268435455), + e.binary('check_meter', ea.STATE, true, false) + .withDescription('Is true if communication problem with meter is experienced'), + ], + configure: async (device, coordinatorEndpoint, logger) => { const endpoint = device.getEndpoint(2); await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering', 'genPowerCfg']); + await reporting.instantaneousDemand(endpoint); + await reporting.readMeteringMultiplierDivisor(endpoint); }, }, { @@ -33,5 +124,6 @@ const definitions: DefinitionWithExtend[] = [ }, ]; + export default definitions; module.exports = definitions; From a24b68c0b1f557c55f487eabdb6d48976f3427dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Sat, 28 Sep 2024 23:28:28 +0200 Subject: [PATCH 2/9] Small adjustments --- src/devices/frient.ts | 66 +++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index 4c8bf5ad24d3b..24a989f711b73 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -1,29 +1,29 @@ -import * as exposes from '../lib/exposes'; +import {Zcl} from 'zigbee-herdsman'; + import fz from '../converters/fromZigbee'; +import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; import {electricityMeter, onOff, ota} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; import {DefinitionWithExtend, Fz, Tz} from '../lib/types'; -import * as reporting from '../lib/reporting' -import * as constants from '../lib/constants' -import * as utils from '../lib/utils' +import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; -// develco specific cosntants -const manufacturerOptions = {manufacturerCode: 0x1015}; - +// frient/develco specific cosntants +const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}; -// develco specific convertors +// frient/develco specific convertors const frient = { fz: { pulse_configuration: { cluster: 'seMetering', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - const result = {}; + const result: Record = {}; if (msg.data.hasOwnProperty('develcoPulseConfiguration')) { - result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = - msg.data['develcoPulseConfiguration']; + result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; } return result; @@ -33,12 +33,13 @@ const frient = { cluster: 'seMetering', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - const result = {}; + const result: Record = {}; if (msg.data.hasOwnProperty('develcoInterfaceMode')) { - result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = - constants.develcoInterfaceMode.hasOwnProperty(msg.data['develcoInterfaceMode']) ? - constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] : - msg.data['develcoInterfaceMode']; + result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = constants.develcoInterfaceMode.hasOwnProperty( + msg.data['develcoInterfaceMode'], + ) + ? constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] + : msg.data['develcoInterfaceMode']; } if (msg.data.hasOwnProperty('status')) { result['battery_low'] = (msg.data.status & 2) > 0; @@ -53,8 +54,8 @@ const frient = { pulse_configuration: { key: ['pulse_configuration'], convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {'develcoPulseConfiguration': value}, manufacturerOptions); - return {readAfterWriteTime: 200, state: {'pulse_configuration': value}}; + await entity.write('seMetering', {develcoPulseConfiguration: value}, utils.getOptions(meta.mapped, entity)); + return {readAfterWriteTime: 200, state: {pulse_configuration: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); @@ -63,9 +64,9 @@ const frient = { interface_mode: { key: ['interface_mode'], convertSet: async (entity, key, value, meta) => { - const payload = {'develcoInterfaceMode': utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)}; - await entity.write('seMetering', payload, manufacturerOptions); - return {readAfterWriteTime: 200, state: {'interface_mode': value}}; + const payload = {develcoInterfaceMode: utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)}; + await entity.write('seMetering', payload, utils.getOptions(meta.mapped, entity)); + return {readAfterWriteTime: 200, state: {interface_mode: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions); @@ -74,8 +75,8 @@ const frient = { current_summation: { key: ['current_summation'], convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions); - return {state: {'current_summation': value}}; + await entity.write('seMetering', {develcoCurrentSummation: value}, utils.getOptions(meta.mapped, entity)); + return {state: {current_summation: value}}; }, } satisfies Tz.Converter, }, @@ -94,16 +95,20 @@ const definitions: DefinitionWithExtend[] = [ e.power(), e.energy(), e.battery_low(), - e.numeric('pulse_configuration', ea.ALL).withValueMin(0).withValueMax(65535) + e + .numeric('pulse_configuration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), - e.enum('interface_mode', ea.ALL, - ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0']) + e + .enum('interface_mode', ea.ALL, ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0']) .withDescription('Operating mode/probe'), - e.numeric('current_summation', ea.SET) - .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh').withValueMin(0) + e + .numeric('current_summation', ea.SET) + .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh') + .withValueMin(0) .withValueMax(268435455), - e.binary('check_meter', ea.STATE, true, false) - .withDescription('Is true if communication problem with meter is experienced'), + e.binary('check_meter', ea.STATE, true, false).withDescription('Is true if communication problem with meter is experienced'), ], configure: async (device, coordinatorEndpoint, logger) => { const endpoint = device.getEndpoint(2); @@ -124,6 +129,5 @@ const definitions: DefinitionWithExtend[] = [ }, ]; - export default definitions; module.exports = definitions; From 7969610894a91faf8c6506aa1f5a947356fa6850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Sat, 28 Sep 2024 23:30:39 +0200 Subject: [PATCH 3/9] Small adjustments take 2 --- src/devices/frient.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index 24a989f711b73..dc27f71fb12fc 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -22,7 +22,7 @@ const frient = { type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result: Record = {}; - if (msg.data.hasOwnProperty('develcoPulseConfiguration')) { + if (msg.data?.develcoPulseConfiguration) { result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; } @@ -34,14 +34,14 @@ const frient = { type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result: Record = {}; - if (msg.data.hasOwnProperty('develcoInterfaceMode')) { - result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = constants.develcoInterfaceMode.hasOwnProperty( - msg.data['develcoInterfaceMode'], - ) + if (msg.data?.develcoInterfaceMode) { + result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = constants.develcoInterfaceMode[ + msg.data['develcoInterfaceMode'] + ] ? constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] : msg.data['develcoInterfaceMode']; } - if (msg.data.hasOwnProperty('status')) { + if (msg.data?.status) { result['battery_low'] = (msg.data.status & 2) > 0; result['check_meter'] = (msg.data.status & 1) > 0; } From 2d77707a44fa36a640615bf65a442a58437512f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Sun, 29 Sep 2024 13:20:27 +0200 Subject: [PATCH 4/9] Remove unnecessary comments --- src/devices/frient.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index dc27f71fb12fc..bea530c036323 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -11,7 +11,7 @@ import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; -// frient/develco specific cosntants +// frient/develco specific constants const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}; // frient/develco specific convertors @@ -84,10 +84,10 @@ const frient = { const definitions: DefinitionWithExtend[] = [ { - zigbeeModel: ['EMIZB-141'], // The model ID from: Device with modelID 'lumi.sens' is not supported. - model: 'EMIZB-141', // Vendor model number, look on the device for a model number - vendor: 'Frient A/S', // Vendor of the device (only used for documentation and startup logging) - description: 'frient Electricity Meter Interface 2 LED', // Description of the device, copy from vendor site. (only used for documentation and startup logging) + zigbeeModel: ['EMIZB-141'], + model: 'EMIZB-141', + vendor: 'Frient A/S', + description: 'frient Electricity Meter Interface 2 LED', fromZigbee: [fz.metering, fz.battery, frient.fz.pulse_configuration, frient.fz.interface_mode], toZigbee: [frient.tz.pulse_configuration, frient.tz.interface_mode, frient.tz.current_summation], extend: [ota()], From d784cbeb78b3ca073c248f2530f8373b4a9f2fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Mon, 30 Sep 2024 21:02:04 +0200 Subject: [PATCH 5/9] Removing unnecessary code --- src/devices/frient.ts | 57 ++++++++++--------------------------------- 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index bea530c036323..9e617e36cabfb 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -1,9 +1,8 @@ import {Zcl} from 'zigbee-herdsman'; -import fz from '../converters/fromZigbee'; -import * as constants from '../lib/constants'; +import {develcoModernExtend} from '../lib/develco'; import * as exposes from '../lib/exposes'; -import {electricityMeter, onOff, ota} from '../lib/modernExtend'; +import {battery, electricityMeter, onOff, ota} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; import {DefinitionWithExtend, Fz, Tz} from '../lib/types'; import * as utils from '../lib/utils'; @@ -11,6 +10,8 @@ import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; +// NOTE! Develco and Frient is the same company, therefore we use develco specific things in here. + // frient/develco specific constants const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}; @@ -26,26 +27,6 @@ const frient = { result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; } - return result; - }, - } satisfies Fz.Converter, - interface_mode: { - cluster: 'seMetering', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: Record = {}; - if (msg.data?.develcoInterfaceMode) { - result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = constants.develcoInterfaceMode[ - msg.data['develcoInterfaceMode'] - ] - ? constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] - : msg.data['develcoInterfaceMode']; - } - if (msg.data?.status) { - result['battery_low'] = (msg.data.status & 2) > 0; - result['check_meter'] = (msg.data.status & 1) > 0; - } - return result; }, } satisfies Fz.Converter, @@ -61,17 +42,6 @@ const frient = { await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); }, } satisfies Tz.Converter, - interface_mode: { - key: ['interface_mode'], - convertSet: async (entity, key, value, meta) => { - const payload = {develcoInterfaceMode: utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)}; - await entity.write('seMetering', payload, utils.getOptions(meta.mapped, entity)); - return {readAfterWriteTime: 200, state: {interface_mode: value}}; - }, - convertGet: async (entity, key, meta) => { - await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions); - }, - } satisfies Tz.Converter, current_summation: { key: ['current_summation'], convertSet: async (entity, key, value, meta) => { @@ -88,27 +58,26 @@ const definitions: DefinitionWithExtend[] = [ model: 'EMIZB-141', vendor: 'Frient A/S', description: 'frient Electricity Meter Interface 2 LED', - fromZigbee: [fz.metering, fz.battery, frient.fz.pulse_configuration, frient.fz.interface_mode], - toZigbee: [frient.tz.pulse_configuration, frient.tz.interface_mode, frient.tz.current_summation], - extend: [ota()], + fromZigbee: [frient.fz.pulse_configuration], + toZigbee: [frient.tz.pulse_configuration, frient.tz.current_summation], + extend: [ + ota(), + electricityMeter({cluster: 'metering', power: {divisor: 1000, multiplier: 1}, energy: {divisor: 1000, multiplier: 1}}), + battery(), + develcoModernExtend.addCustomClusterManuSpecificDevelcoGenBasic(), + develcoModernExtend.readGenBasicPrimaryVersions(), + ], exposes: [ - e.power(), - e.energy(), - e.battery_low(), e .numeric('pulse_configuration', ea.ALL) .withValueMin(0) .withValueMax(65535) .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), - e - .enum('interface_mode', ea.ALL, ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0']) - .withDescription('Operating mode/probe'), e .numeric('current_summation', ea.SET) .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh') .withValueMin(0) .withValueMax(268435455), - e.binary('check_meter', ea.STATE, true, false).withDescription('Is true if communication problem with meter is experienced'), ], configure: async (device, coordinatorEndpoint, logger) => { const endpoint = device.getEndpoint(2); From b4282213e0486dbafbb4c7fd8e92480c10734045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Mon, 30 Sep 2024 21:32:29 +0200 Subject: [PATCH 6/9] Convert to modernExtend functions --- src/devices/frient.ts | 113 +++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index 9e617e36cabfb..ca3d4e5cf3911 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -4,7 +4,7 @@ import {develcoModernExtend} from '../lib/develco'; import * as exposes from '../lib/exposes'; import {battery, electricityMeter, onOff, ota} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; -import {DefinitionWithExtend, Fz, Tz} from '../lib/types'; +import {DefinitionWithExtend, Fz, ModernExtend, Tz} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; @@ -15,42 +15,66 @@ const ea = exposes.access; // frient/develco specific constants const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}; -// frient/develco specific convertors -const frient = { - fz: { - pulse_configuration: { - cluster: 'seMetering', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: Record = {}; - if (msg.data?.develcoPulseConfiguration) { - result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; - } +function current_summation() { + return { + isModernExtend: true, + toZigbee: [ + { + key: ['current_summation'], + convertSet: async (entity, key, value, meta) => { + await entity.write('seMetering', {develcoCurrentSummation: value}, utils.getOptions(meta.mapped, entity)); + return {state: {current_summation: value}}; + }, + } satisfies Tz.Converter, + ], + exposes: [ + e + .numeric('current_summation', ea.SET) + .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh') + .withValueMin(0) + .withValueMax(268435455), + ], + } satisfies ModernExtend; +} - return result; - }, - } satisfies Fz.Converter, - }, - tz: { - pulse_configuration: { - key: ['pulse_configuration'], - convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {develcoPulseConfiguration: value}, utils.getOptions(meta.mapped, entity)); - return {readAfterWriteTime: 200, state: {pulse_configuration: value}}; - }, - convertGet: async (entity, key, meta) => { - await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); - }, - } satisfies Tz.Converter, - current_summation: { - key: ['current_summation'], - convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {develcoCurrentSummation: value}, utils.getOptions(meta.mapped, entity)); - return {state: {current_summation: value}}; - }, - } satisfies Tz.Converter, - }, -}; +function pulse_configuration() { + return { + isModernExtend: true, + fromZigbee: [ + { + cluster: 'seMetering', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: Record = {}; + if (msg.data?.develcoPulseConfiguration) { + result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; + } + + return result; + }, + } satisfies Fz.Converter, + ], + toZigbee: [ + { + key: ['pulse_configuration'], + convertSet: async (entity, key, value, meta) => { + await entity.write('seMetering', {develcoPulseConfiguration: value}, utils.getOptions(meta.mapped, entity)); + return {readAfterWriteTime: 200, state: {pulse_configuration: value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); + }, + } satisfies Tz.Converter, + ], + exposes: [ + e + .numeric('pulse_configuration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), + ], + } satisfies ModernExtend; +} const definitions: DefinitionWithExtend[] = [ { @@ -58,27 +82,16 @@ const definitions: DefinitionWithExtend[] = [ model: 'EMIZB-141', vendor: 'Frient A/S', description: 'frient Electricity Meter Interface 2 LED', - fromZigbee: [frient.fz.pulse_configuration], - toZigbee: [frient.tz.pulse_configuration, frient.tz.current_summation], extend: [ ota(), electricityMeter({cluster: 'metering', power: {divisor: 1000, multiplier: 1}, energy: {divisor: 1000, multiplier: 1}}), battery(), develcoModernExtend.addCustomClusterManuSpecificDevelcoGenBasic(), develcoModernExtend.readGenBasicPrimaryVersions(), + pulse_configuration(), + current_summation(), ], - exposes: [ - e - .numeric('pulse_configuration', ea.ALL) - .withValueMin(0) - .withValueMax(65535) - .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), - e - .numeric('current_summation', ea.SET) - .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh') - .withValueMin(0) - .withValueMax(268435455), - ], + configure: async (device, coordinatorEndpoint, logger) => { const endpoint = device.getEndpoint(2); await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering', 'genPowerCfg']); From 965fad74fb5ac1ca27d5bdd6bad559e127111d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Mon, 30 Sep 2024 21:43:54 +0200 Subject: [PATCH 7/9] Move to modernExtend --- src/devices/frient.ts | 77 ++----------------------------------------- src/lib/develco.ts | 60 ++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 75 deletions(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index ca3d4e5cf3911..2028b40a0a5bf 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -1,81 +1,10 @@ -import {Zcl} from 'zigbee-herdsman'; - import {develcoModernExtend} from '../lib/develco'; -import * as exposes from '../lib/exposes'; import {battery, electricityMeter, onOff, ota} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; -import {DefinitionWithExtend, Fz, ModernExtend, Tz} from '../lib/types'; -import * as utils from '../lib/utils'; - -const e = exposes.presets; -const ea = exposes.access; +import {DefinitionWithExtend} from '../lib/types'; // NOTE! Develco and Frient is the same company, therefore we use develco specific things in here. -// frient/develco specific constants -const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}; - -function current_summation() { - return { - isModernExtend: true, - toZigbee: [ - { - key: ['current_summation'], - convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {develcoCurrentSummation: value}, utils.getOptions(meta.mapped, entity)); - return {state: {current_summation: value}}; - }, - } satisfies Tz.Converter, - ], - exposes: [ - e - .numeric('current_summation', ea.SET) - .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh') - .withValueMin(0) - .withValueMax(268435455), - ], - } satisfies ModernExtend; -} - -function pulse_configuration() { - return { - isModernExtend: true, - fromZigbee: [ - { - cluster: 'seMetering', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: Record = {}; - if (msg.data?.develcoPulseConfiguration) { - result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; - } - - return result; - }, - } satisfies Fz.Converter, - ], - toZigbee: [ - { - key: ['pulse_configuration'], - convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {develcoPulseConfiguration: value}, utils.getOptions(meta.mapped, entity)); - return {readAfterWriteTime: 200, state: {pulse_configuration: value}}; - }, - convertGet: async (entity, key, meta) => { - await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); - }, - } satisfies Tz.Converter, - ], - exposes: [ - e - .numeric('pulse_configuration', ea.ALL) - .withValueMin(0) - .withValueMax(65535) - .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), - ], - } satisfies ModernExtend; -} - const definitions: DefinitionWithExtend[] = [ { zigbeeModel: ['EMIZB-141'], @@ -88,8 +17,8 @@ const definitions: DefinitionWithExtend[] = [ battery(), develcoModernExtend.addCustomClusterManuSpecificDevelcoGenBasic(), develcoModernExtend.readGenBasicPrimaryVersions(), - pulse_configuration(), - current_summation(), + develcoModernExtend.pulse_configuration(), + develcoModernExtend.current_summation(), ], configure: async (device, coordinatorEndpoint, logger) => { diff --git a/src/lib/develco.ts b/src/lib/develco.ts index f806d640ba248..2416b83ae478a 100644 --- a/src/lib/develco.ts +++ b/src/lib/develco.ts @@ -2,7 +2,8 @@ import {Zcl} from 'zigbee-herdsman'; import {presets as e, access as ea} from './exposes'; import {deviceAddCustomCluster, deviceTemperature, numeric, NumericArgs, temperature} from './modernExtend'; -import {Configure, Fz, ModernExtend} from './types'; +import {Configure, Fz, ModernExtend, Tz} from './types'; +import {getOptions, postfixWithEndpointName} from './utils'; const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}; @@ -170,4 +171,61 @@ export const develcoModernExtend = { valueIgnore: [0xffff, -0x8000], ...args, }), + current_summation: () => + ({ + isModernExtend: true, + toZigbee: [ + { + key: ['current_summation'], + convertSet: async (entity, key, value, meta) => { + await entity.write('seMetering', {develcoCurrentSummation: value}, getOptions(meta.mapped, entity)); + return {state: {current_summation: value}}; + }, + } satisfies Tz.Converter, + ], + exposes: [ + e + .numeric('current_summation', ea.SET) + .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh') + .withValueMin(0) + .withValueMax(268435455), + ], + }) satisfies ModernExtend, + pulse_configuration: () => + ({ + isModernExtend: true, + fromZigbee: [ + { + cluster: 'seMetering', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: Record = {}; + if (msg.data?.develcoPulseConfiguration) { + result[postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; + } + + return result; + }, + } satisfies Fz.Converter, + ], + toZigbee: [ + { + key: ['pulse_configuration'], + convertSet: async (entity, key, value, meta) => { + await entity.write('seMetering', {develcoPulseConfiguration: value}, getOptions(meta.mapped, entity)); + return {readAfterWriteTime: 200, state: {pulse_configuration: value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); + }, + } satisfies Tz.Converter, + ], + exposes: [ + e + .numeric('pulse_configuration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), + ], + }) satisfies ModernExtend, }; From 5b21bf6ae17efd917f62bba08a0b8e6f82c37c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Mon, 30 Sep 2024 21:57:01 +0200 Subject: [PATCH 8/9] Rename vendor --- src/devices/frient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/frient.ts b/src/devices/frient.ts index 2028b40a0a5bf..6a09a0ec33dae 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -9,7 +9,7 @@ const definitions: DefinitionWithExtend[] = [ { zigbeeModel: ['EMIZB-141'], model: 'EMIZB-141', - vendor: 'Frient A/S', + vendor: 'Frient', description: 'frient Electricity Meter Interface 2 LED', extend: [ ota(), From d740ec0ebf49de654ef585c9e4fad6c24c51fedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Bowman=20M=C3=B8rch?= Date: Tue, 1 Oct 2024 07:56:37 +0200 Subject: [PATCH 9/9] Default multiplier/divisorvalues --- src/converters/fromZigbee.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/converters/fromZigbee.ts b/src/converters/fromZigbee.ts index 691ed1b25fe79..aab0619c06f71 100644 --- a/src/converters/fromZigbee.ts +++ b/src/converters/fromZigbee.ts @@ -740,8 +740,8 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { if (utils.hasAlreadyProcessedMessage(msg, model)) return; const payload: KeyValueAny = {}; - const multiplier = msg.endpoint.getClusterAttributeValue('seMetering', 'multiplier') as number; - const divisor = msg.endpoint.getClusterAttributeValue('seMetering', 'divisor') as number; + const multiplier = (msg.endpoint.getClusterAttributeValue('seMetering', 'multiplier') ?? 1) as number; + const divisor = (msg.endpoint.getClusterAttributeValue('seMetering', 'divisor') ?? 1000) as number; const factor = multiplier && divisor ? multiplier / divisor : null; if (msg.data.instantaneousDemand !== undefined) {