zigbee2mqtt icon indicating copy to clipboard operation
zigbee2mqtt copied to clipboard

[New device support]: EMIZB-141 Power Meter

Open NicolaiKobras opened this issue 2 years ago • 111 comments

Link

https://www.amazon.de/Electricity-Zählerauslesung-Energieverbrauchsüberwachung-LED-Impulsen-funktioniert/dp/B0CGVB6LGC

Database entry

{"id":54,"type":"EndDevice","ieeeAddr":"0x0015bc001b100a56","nwkAddr":51420,"manufId":4117,"manufName":"frient A/S","powerSource":"Battery","modelId":"EMIZB-141","epList":[1,2],"endpoints":{"1":{"profId":49353,"epId":1,"devId":1,"inClusterList":[5,6],"outClusterList":[],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}},"2":{"profId":260,"epId":2,"devId":83,"inClusterList":[0,1,3,32,1794,2817,2821],"outClusterList":[3,10,25],"clusters":{"genBasic":{"attributes":{}},"genPollCtrl":{"attributes":{"checkinInterval":14400}},"seMetering":{"attributes":{"currentSummDelivered":[0],"instantaneousDemand":354,"multiplier":1,"divisor":1000,"develcoPulseConfiguration":10000,"develcoInterfaceMode":0}}},"binds":[{"cluster":32,"type":"endpoint","deviceIeeeAddress":"0x00124b0024c2ad1c","endpointID":1},{"cluster":1794,"type":"endpoint","deviceIeeeAddress":"0x00124b0024c2ad1c","endpointID":1}],"configuredReportings":[{"cluster":1794,"attrId":1024,"minRepIntval":5,"maxRepIntval":3600,"repChange":1}],"meta":{}}},"dateCode":"2023-09-06 11:54","zclVersion":7,"interviewCompleted":true,"meta":{"configured":-570313272},"lastSeen":1695324832904,"defaultSendRequestWhen":"active","checkinInterval":3600}

Comments

It works reasonable, but shows no battery for example. I basically just copied an old Version of the previous version (https://www.zigbee2mqtt.io/devices/ZHEMI101.html) I only linked a German Amazon Page, because the Vendor doesn't have a site for the product yet (this is the old version: https://frient.com/products/electricity-meter-interface/)

External converter

const exposes = require('zigbee-herdsman-converters/lib/exposes');
const fz = {...require('zigbee-herdsman-converters/converters/fromZigbee'), legacy: require('zigbee-herdsman-converters/lib/legacy').fromZigbee};
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const constants = require('zigbee-herdsman-converters/lib/constants');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const globalStore = require('zigbee-herdsman-converters/lib/store');
const utils = require('zigbee-herdsman-converters/lib/utils');
const ota = require('zigbee-herdsman-converters/lib/ota');
const e = exposes.presets;
const ea = exposes.access;

// develco specific cosntants
const manufacturerOptions = {manufacturerCode: 0x1015};

/* MOSZB-1xx - ledControl - bitmap8 - r/w
 * 0x00 Disable LED when movement is detected.
 * 0x01 Enables periodic fault flashes. These flashes are used to indicate e.g. low battery level.
 * 0x02 Enables green application defined LED. This is e.g. used to indicate motion detection.
 * Default value 0xFF ( seems to be fault + motion)
 */
const develcoLedControlMap = {
    0x00: 'off',
    0x01: 'fault_only',
    0x02: 'motion_only',
    0xFF: 'both',
};


// develco specific convertors
const develco = {
    configure: {
        read_sw_hw_version: async (device, logger) => {
            for (const ep of device.endpoints) {
                if (ep.supportsInputCluster('genBasic')) {
                    try {
                        const data = await ep.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'],
                            manufacturerOptions);

                        if (data.hasOwnProperty('develcoPrimarySwVersion')) {
                            device.softwareBuildID = data.develcoPrimarySwVersion.join('.');
                        }

                        if (data.hasOwnProperty('develcoPrimaryHwVersion')) {
                            device.hardwareVersion = data.develcoPrimaryHwVersion.join('.');
                        }
                    } catch (error) {/* catch timeouts of sleeping devices */}
                    break;
                }
            }
        },
    },
    fz: {
        // Some Develco devices report strange values sometimes
        // https://github.com/Koenkk/zigbee2mqtt/issues/13329
        electrical_measurement: {
            ...fz.electrical_measurement,
            convert: (model, msg, publish, options, meta) => {
                if (msg.data.rmsVoltage !== 0xFFFF && msg.data.rmsCurrent !== 0xFFFF && msg.data.activePower !== -0x8000) {
                    return fz.electrical_measurement.convert(model, msg, publish, options, meta);
                }
            },
        },
        device_temperature: {
            ...fz.device_temperature,
            convert: (model, msg, publish, options, meta) => {
                if (msg.data.currentTemperature !== -0x8000) {
                    return fz.device_temperature.convert(model, msg, publish, options, meta);
                }
            },
        },
        temperature: {
            ...fz.temperature,
            convert: (model, msg, publish, options, meta) => {
                if (msg.data.measuredValue !== -0x8000 && msg.data.measuredValue !== 0xFFFF) {
                    return fz.temperature.convert(model, msg, publish, options, meta);
                }
            },
        },
        metering: {
            ...fz.metering,
            convert: (model, msg, publish, options, meta) => {
                if (msg.data.instantaneousDemand !== -0x800000) {
                    return fz.metering.convert(model, msg, publish, options, meta);
                }
            },
        },
        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;
            },
        },
        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;
            },
        },
        fault_status: {
            cluster: 'genBinaryInput',
            type: ['attributeReport', 'readResponse'],
            convert: (model, msg, publish, options, meta) => {
                const result = {};
                if (msg.data.hasOwnProperty('reliability')) {
                    const lookup = {0: 'no_fault_detected', 7: 'unreliable_other', 8: 'process_error'};
                    result.reliability = lookup[msg.data['reliability']];
                }
                if (msg.data.hasOwnProperty('statusFlags')) {
                    result.fault = (msg.data['statusFlags']===1);
                }
                return result;
            },
        },
        voc: {
            cluster: 'develcoSpecificAirQuality',
            type: ['attributeReport', 'readResponse'],
            options: [exposes.options.precision('voc'), exposes.options.calibration('voc')],
            convert: (model, msg, publish, options, meta) => {
                // from Sensirion_Gas_Sensors_SGP3x_TVOC_Concept.pdf
                // "The mean molar mass of this mixture is 110 g/mol and hence,
                // 1 ppb TVOC corresponds to 4.5 μg/m3."
                const vocPpb = parseFloat(msg.data['measuredValue']);
                const voc = vocPpb * 4.5;
                const vocProperty = utils.postfixWithEndpointName('voc', msg, model, meta);

                // from aqszb-110-technical-manual-air-quality-sensor-04-08-20.pdf page 6, section 2.2 voc
                // this contains a ppb to level mapping table.
                let airQuality;
                const airQualityProperty = utils.postfixWithEndpointName('air_quality', msg, model, meta);
                if (vocPpb <= 65) {
                    airQuality = 'excellent';
                } else if (vocPpb <= 220) {
                    airQuality = 'good';
                } else if (vocPpb <= 660) {
                    airQuality = 'moderate';
                } else if (vocPpb <= 2200) {
                    airQuality = 'poor';
                } else if (vocPpb <= 5500) {
                    airQuality = 'unhealthy';
                } else if (vocPpb > 5500) {
                    airQuality = 'out_of_range';
                } else {
                    airQuality = 'unknown';
                }
                return {[vocProperty]: utils.calibrateAndPrecisionRoundOptions(voc, options, 'voc'), [airQualityProperty]: airQuality};
            },
        },
        voc_battery: {
            cluster: 'genPowerCfg',
            type: ['attributeReport', 'readResponse'],
            convert: (model, msg, publish, options, meta) => {
                /*
                 * Per the technical documentation for AQSZB-110:
                 * To detect low battery the system can monitor the "BatteryVoltage" by setting up a reporting interval of every 12 hour.
                 * When a voltage of 2.5V is measured the battery should be replaced.
                 * Low batt LED indication–RED LED will blink twice every 60 second.
                 */
                const result = fz.battery.convert(model, msg, publish, options, meta);
                result.battery_low = (result.voltage <= 2500);
                return result;
            },
        },
        led_control: {
            cluster: 'genBasic',
            type: ['attributeReport', 'readResponse'],
            options: [],
            convert: (model, msg, publish, options, meta) => {
                const state = {};

                if (msg.data.hasOwnProperty('develcoLedControl')) {
                    state['led_control'] = develcoLedControlMap[msg.data['develcoLedControl']];
                }

                return state;
            },
        },
        ias_occupancy_timeout: {
            cluster: 'ssIasZone',
            type: ['attributeReport', 'readResponse'],
            options: [],
            convert: (model, msg, publish, options, meta) => {
                const state = {};

                if (msg.data.hasOwnProperty('develcoAlarmOffDelay')) {
                    state['occupancy_timeout'] = msg.data['develcoAlarmOffDelay'];
                }

                return state;
            },
        },
        input: {
            cluster: 'genBinaryInput',
            type: ['attributeReport', 'readResponse'],
            convert: (model, msg, publish, options, meta) => {
                const result = {};
                if (msg.data.hasOwnProperty('presentValue')) {
                    const value = msg.data['presentValue'];
                    result[utils.postfixWithEndpointName('input', msg, model, meta)] = value == 1;
                }
                return result;
            },
        },
    },
    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);
            },
        },
        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);
            },
        },
        current_summation: {
            key: ['current_summation'],
            convertSet: async (entity, key, value, meta) => {
                await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions);
                return {state: {'current_summation': value}};
            },
        },
        led_control: {
            key: ['led_control'],
            convertSet: async (entity, key, value, meta) => {
                const ledControl = utils.getKey(develcoLedControlMap, value, value, Number);
                await entity.write('genBasic', {'develcoLedControl': ledControl}, manufacturerOptions);
                return {state: {led_control: value}};
            },
            convertGet: async (entity, key, meta) => {
                await entity.read('genBasic', ['develcoLedControl'], manufacturerOptions);
            },
        },
        ias_occupancy_timeout: {
            key: ['occupancy_timeout'],
            convertSet: async (entity, key, value, meta) => {
                let timeoutValue = value;
                if (timeoutValue < 20) {
                    meta.logger.warn(`Minimum occupancy_timeout is 20, using 20 instead of ${timeoutValue}!`);
                    timeoutValue = 20;
                }
                await entity.write('ssIasZone', {'develcoAlarmOffDelay': timeoutValue}, manufacturerOptions);
                return {state: {occupancy_timeout: timeoutValue}};
            },
            convertGet: async (entity, key, meta) => {
                await entity.read('ssIasZone', ['develcoAlarmOffDelay'], manufacturerOptions);
            },
        },
        input: {
            key: ['input'],
            convertGet: async (entity, key, meta) => {
                await entity.read('genBinaryInput', ['presentValue']);
            },
        },
    },
};


const definition = {
    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 Powermeter', // Description of the device, copy from vendor site. (only used for documentation and startup logging)
    fromZigbee: [develco.fz.metering, develco.fz.pulse_configuration, develco.fz.interface_mode],
        toZigbee: [develco.tz.pulse_configuration, develco.tz.interface_mode, develco.tz.current_summation],
        endpoint: (device) => {
            return {'default': 2};
        },
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint = device.getEndpoint(2);
            await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']);
            await reporting.instantaneousDemand(endpoint);
            await reporting.readMeteringMultiplierDivisor(endpoint);
        },
        exposes: [
            e.power(),
            e.energy(),
            e.battery_low(),
            exposes.numeric('pulse_configuration', ea.ALL).withValueMin(0).withValueMax(65535)
                .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'),
            exposes.enum('interface_mode', ea.ALL,
                ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0'])
                .withDescription('Operating mode/probe'),
            exposes.numeric('current_summation', ea.SET)
                .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh').withValueMin(0)
                .withValueMax(268435455),
            exposes.binary('check_meter', ea.STATE, true, false)
                .withDescription('Is true if communication problem with meter is experienced'),
        ],
};

module.exports = definition;

Supported color modes

No response

Color temperature range

No response

NicolaiKobras avatar Sep 21 '23 19:09 NicolaiKobras

Could you make a PR to add support for this device?

Koenkk avatar Sep 23 '23 07:09 Koenkk

I am still testing and experiencing some weirdness, where I see less usage than in reality, however, I don’t know if it is due to my power meter or the device, since I have no other reader device to compare to.

NicolaiKobras avatar Sep 25 '23 07:09 NicolaiKobras

I could need some help with this :)

Readings are definitely not accurate when set to the right frequency, also another problem I have noticed is that I now have a small solar system and it seems to add the generated overflow energy that goes out to the total.

NicolaiKobras avatar Oct 01 '23 11:10 NicolaiKobras

I've encountered the same problem, it's mentioned even on develco website that this is not compatible with PV systems, it doesn't happen to 100% of electrometers, but some of them are configured to give impulse also on energy export and you have no way of distinguishing import from export (if you have on-grid setup for solar)

j91321 avatar Oct 01 '23 11:10 j91321

my system seems to have 2 IR Lights, so I wonder if one is dedicated for export and one for import, might test this out by just covering one up.

NicolaiKobras avatar Oct 01 '23 11:10 NicolaiKobras

Many systems have multiple lights, my also has 2, but one shows imp./kWh the other imp./kvarh. Best to find and consult manual.

j91321 avatar Oct 01 '23 11:10 j91321

That would be a bummer, since to my knowledge this is the only zigbee power meter, that’s why I bought it and this makes it practically useless :/ Probably will have to send it back if this is the case.

NicolaiKobras avatar Oct 01 '23 12:10 NicolaiKobras

There are other power meters like TuYa TS0601 although the installation is definitely more complex than the LED one. Based on my research so far this would also work better with PV because, it should be able to determine the import/export direction depending on where the clamps are installed.

j91321 avatar Oct 01 '23 12:10 j91321

Will this device be officially supported ? I am considering it, as it seems the device with the most simple installation out there … https://www.develcoproducts.com/de/produkte/zaehlermodule/modul-fuer-stromzaehler-2-led/#EMI2LEDAngaben

KaytheDays avatar Oct 02 '23 14:10 KaytheDays

Hello, i'm new to Zigbee2mqtt, how can i import the config shown by lSh4dowl ?

LifeInZeitnot avatar Oct 13 '23 16:10 LifeInZeitnot

I had same problem yesterday, this worked for me:

Via the web terminal, I could create my converter file per the Z2M guide and place it in /config/zigbee2mqtt/.js (that's what the guide means by "This file has to be created next to the configuration.yaml", because the configuration.yaml file for Z2M is in /config/zigbee2mqtt, at least in HassOS).

Then, to tell Z2M to use the converter file, go to the Z2M UI, then click "Settings" > "External Converters". Enter the filename of the converter file you just created ("SPP02GIP.js" in my case), and click "Submit", then "Restart".

The external converter file should now be in use by Z2M, which you can verify in the logs per the guide linked in the OP.

petosiso avatar Oct 17 '23 11:10 petosiso

@petosiso Can you confirm, that the device is working correctly when you don't have a solar system?

AlmightyCZ avatar Nov 01 '23 05:11 AlmightyCZ

Hi @petosiso. I can confirm it's working well on a non-solar system meter. Have had it running for about 3 weeks (using the custom converter shared by @lSh4dowl. Including showing up in Energy Dashboard.

There are issues with some fields (Battery mainly) and the Summation resets to Zero no matter what is entered. I was hoping to use that latter field to input my starting Meter number and have it increase from there but from reading back it appears that was an issue with the v1 meter also. Maybe when 'proper' support comes to Z2M it will work but for the moment it is more than enough for me.

I've also set up Utility meters for Hour, Day & Month so I can see a visual. I used a version of this article here to set up my Day, Night & Peak rates. https://community.home-assistant.io/t/stuck-setting-up-peak-off-peak-electricity-tariff/419894/5

image

image

image

penroseg avatar Nov 02 '23 18:11 penroseg

@AlmightyCZ yes, I can also confirm that it works. Exactly as @penroseg described, image image

petosiso avatar Nov 03 '23 10:11 petosiso

Does anyone knows when is this to be added to the list of supported devices?

jllarraz avatar Nov 23 '23 08:11 jllarraz

It's now supported in 1.34.0(https://github.com/Koenkk/zigbee-herdsman-converters/pull/6582 + https://www.zigbee2mqtt.io/devices/EMIZB-141.html)

AlmightyCZ avatar Dec 02 '23 08:12 AlmightyCZ

Great news that it is now supported in 1.34.0, but for some reason I don't get an energy reading. Does anybody face the same issue or have a fix? I tried reconfiguring. Skærmbillede 2023-12-02 kl  11 55 09

cawith avatar Dec 02 '23 10:12 cawith

Great news that it is now supported in 1.34.0, but for some reason I don't get an energy reading. Does anybody face the same issue or have a fix? I tried reconfiguring. Skærmbillede 2023-12-02 kl 11 55 09

you have to set the pulse setting which isnt there in the official build, im currently working on a pr, or use the custom external converter

ywaf avatar Dec 03 '23 11:12 ywaf

I've encountered the same problem, it's mentioned even on develco website that this is not compatible with PV systems, it doesn't happen to 100% of electrometers, but some of them are configured to give impulse also on energy export and you have no way of distinguishing import from export (if you have on-grid setup for solar)

So this is a hardware issue and the device support in Z2MQTT doesn't make any difference?

alerich-x avatar Dec 06 '23 11:12 alerich-x

This custom converter works so much better than what is currently in the release version, so eagerly waiting this to be the officially released!

akostamo avatar Dec 11 '23 14:12 akostamo

Great news that it is now supported in 1.34.0, but for some reason I don't get an energy reading. Does anybody face the same issue or have a fix? I tried reconfiguring.

you have to set the pulse setting which isnt there in the official build, im currently working on a pr, or use the custom external converter

That is good news. What are your plans on the PR? I am considering using the external converter to make energy work, but this might interfere with the PR, right?

dawiinci avatar Dec 13 '23 10:12 dawiinci

Great news that it is now supported in 1.34.0, but for some reason I don't get an energy reading. Does anybody face the same issue or have a fix? I tried reconfiguring.

you have to set the pulse setting which isnt there in the official build, im currently working on a pr, or use the custom external converter

That is good news. What are your plans on the PR? I am considering using the external converter to make energy work, but this might interfere with the PR, right?

nah just remove the custom converter if u wanna go back to normal converter

ywaf avatar Dec 13 '23 10:12 ywaf

In the external converter I am missing the battery indicator in %. Is there a way to add it?

dawiinci avatar Dec 22 '23 07:12 dawiinci

In the external converter I am missing the battery indicator in %. Is there a way to add it?

The converter in release is reporting battery percentage, but it's probably not working. Despite showing 100% consistently, my meter stopped updating (after 60 days of operation), indicating battery depletion. After replacing the batteries, it started working correctly again.

AlmightyCZ avatar Dec 30 '23 18:12 AlmightyCZ

Also the energy reporting has stoped working (as someone has already mentioned here) so I'm going back to the original external converter for now.

AlmightyCZ avatar Dec 30 '23 19:12 AlmightyCZ

I will agree with the use of external converter. It is much more complete. I managed to set the current summation but to be fair not sure what should be. Is it the "current value" in kWh that I see in the power box? (for the Czech people I have PRE).

fumantsu avatar Jan 04 '24 12:01 fumantsu

Used the external converter mentioned above. Running for 5 days and the tracking of my usage matches the electricity meter perfectly.

nivek1612 avatar Jan 09 '24 18:01 nivek1612

I have used the external converter but the values (energy and power) seems a bit odd to me, can someone point me in the right direction { "battery": 60, "battery_low": false, "energy": 4295032.83, "interface_mode": 65534, "linkquality": 255, "power": -8388608, "pulse_configuration": 1000, "voltage": 2700, "check_meter": null, "current_summation": null }

jllarraz avatar Jan 25 '24 16:01 jllarraz

I have used the external converter but the values (energy and power) seems a bit odd to me, can someone point me in the right direction { "battery": 60, "battery_low": false, "energy": 4295032.83, "interface_mode": 65534, "linkquality": 255, "power": -8388608, "pulse_configuration": 1000, "voltage": 2700, "check_meter": null, "current_summation": null }

Check from your energy meter, there should be pulse rate mentioned near the pulse led. Default is 1000 per kwh like in my example picture attached. Just adjust the pulse configuration.

Screenshot_2024-01-25-19-48-50-19_ab7988c7b00b15bc78ec5a428c58236f

akostamo avatar Jan 25 '24 17:01 akostamo

I have used the external converter but the values (energy and power) seems a bit odd to me, can someone point me in the right direction { "battery": 60, "battery_low": false, "energy": 4295032.83, "interface_mode": 65534, "linkquality": 255, "power": -8388608, "pulse_configuration": 1000, "voltage": 2700, "check_meter": null, "current_summation": null }

Check from your energy meter, there should be pulse rate mentioned near the pulse led. Default is 1000 per kwh like in my example picture attached. Just adjust the pulse configuration.

Screenshot_2024-01-25-19-48-50-19_ab7988c7b00b15bc78ec5a428c58236f

I checked and is 1000 imp/kwh so wondering if I have to do something else

jllarraz avatar Jan 26 '24 09:01 jllarraz