### Link
https://www.amazon.de/Electricity-Zählerauslesung-Energieverbrauchsübe…rwachung-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
```shell
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_