Closed Bram81 closed 2 years ago
I think you can do it in Domoticz. Not, in the converter. Otherwise, you should implement your fz.ptvo_switch_analog_input function.
I think you can do it in Domoticz. Not, in the converter. Otherwise, you should implement your fz.ptvo_switch_analog_input function.
Hi, Thank you for the quick reply. Unfortunately in Domoticz you can't make any custom kind of template for a device the way it can be done in HA. Could you give a short example of the way I should implement the fz.ptvo_switch_analog_input function?
Hello @Bram81,
I'm sorry about the delay with my answer. Here is the customized code of a custom converter. I've added fz.ptvo_switch_analog_input2
after fz.legacy = ptvo_switch.meta.tuyaThermostatPreset
. You can find the "scale" constant in the code. This scale factor will be applied for all analog values.
fz.ptvo_switch_analog_input2 = {
cluster: 'genAnalogInput',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const payload = {};
const channel = msg.endpoint.ID;
const name = `l${channel}`;
const endpoint = msg.endpoint;
const scale = 1;
payload[name] = precisionRound(msg.data['presentValue'], 3);
payload[name] *= scale;
const cluster = 'genLevelCtrl';
if (endpoint && (endpoint.supportsInputCluster(cluster) || endpoint.supportsOutputCluster(cluster))) {
payload['brightness_' + name] = msg.data['presentValue'];
} else if (msg.data.hasOwnProperty('description')) {
const data1 = msg.data['description'];
if (data1) {
const data2 = data1.split(',');
const devid = data2[1];
const unit = data2[0];
if (devid) {
payload['device_' + name] = devid;
}
const valRaw = msg.data['presentValue'];
if (unit) {
let val = precisionRound(valRaw, 1);
const nameLookup = {
'C': 'temperature',
'%': 'humidity',
'm': 'altitude',
'Pa': 'pressure',
'ppm': 'quality',
'psize': 'particle_size',
'V': 'voltage',
'A': 'current',
'Wh': 'energy',
'W': 'power',
'Hz': 'frequency',
'pf': 'power_factor',
'lx': 'illuminance_lux',
};
let nameAlt = '';
if (unit === 'A') {
if (valRaw < 1) {
val = precisionRound(valRaw, 3);
}
}
if (unit.startsWith('mcpm') || unit.startsWith('ncpm')) {
const num = unit.substr(4, 1);
nameAlt = (num === 'A')? unit.substr(0, 4) + '10': unit;
val = precisionRound(valRaw, 2);
} else {
nameAlt = nameLookup[unit];
}
if (nameAlt === undefined) {
const valueIndex = parseInt(unit, 10);
if (! isNaN(valueIndex)) {
nameAlt = 'val' + unit;
}
}
if (nameAlt !== undefined) {
payload[nameAlt + '_' + name] = val * scale;
}
}
}
}
return payload;
}
};
Then change fz.ptvo_switch_analog_input
to fz.ptvo_switch_analog_input2
in fromZigbee: [...],
Hi, thank you very much! No worries about the delay, realy appreciate you trying to help me out. I can see what the code has to do, but I'm a complete noob when it comes to java.. Where in the code should I implement the scale, saying 1700 is 0% and 800 = 100%?
If you can show me a formula where X is a source value, I can show you where you should place it in the code above.
Hi, that would be great!
The formula would be ( (Vdry - voltage_l1) / (Vdry-Vwet) ) * 100 . So lets say Vdry is 1770 and Vwet is 835 and X is the source value measured, than it would be:
( (1770- X) / (1770-835) ) * 100
The updated converter with the formula:
function precisionRound(number, precision) {
if (typeof precision === 'number') {
const factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
} else if (typeof precision === 'object') {
const thresholds = Object.keys(precision).map(Number).sort((a, b) => b - a);
for (const t of thresholds) {
if (! isNaN(t) && number >= t) {
return precisionRound(number, precision[t]);
}
}
}
return number;
}
fz.ptvo_switch_analog_input2 = {
cluster: 'genAnalogInput',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const payload = {};
const channel = msg.endpoint.ID;
const name = `l${channel}`;
const endpoint = msg.endpoint;
const value_mv = msg.data['presentValue'] * 1000;
const value_scaled = ( (1770- value_mv ) / (1770-835) ) * 100;
payload[name] = precisionRound(value_scaled , 3);
const cluster = 'genLevelCtrl';
if (endpoint && (endpoint.supportsInputCluster(cluster) || endpoint.supportsOutputCluster(cluster))) {
payload['brightness_' + name] = msg.data['presentValue'];
} else if (msg.data.hasOwnProperty('description')) {
const data1 = msg.data['description'];
if (data1) {
const data2 = data1.split(',');
const devid = data2[1];
const unit = data2[0];
if (devid) {
payload['device_' + name] = devid;
}
const valRaw = msg.data['presentValue'];
if (unit) {
let val = precisionRound(valRaw, 1);
const nameLookup = {
'C': 'temperature',
'%': 'humidity',
'm': 'altitude',
'Pa': 'pressure',
'ppm': 'quality',
'psize': 'particle_size',
'V': 'voltage',
'A': 'current',
'Wh': 'energy',
'W': 'power',
'Hz': 'frequency',
'pf': 'power_factor',
'lx': 'illuminance_lux',
};
let nameAlt = '';
if (unit === 'A') {
if (valRaw < 1) {
val = precisionRound(valRaw, 3);
}
}
if (unit.startsWith('mcpm') || unit.startsWith('ncpm')) {
const num = unit.substr(4, 1);
nameAlt = (num === 'A')? unit.substr(0, 4) + '10': unit;
val = precisionRound(valRaw, 2);
} else {
nameAlt = nameLookup[unit];
}
if (nameAlt === undefined) {
const valueIndex = parseInt(unit, 10);
if (! isNaN(valueIndex)) {
nameAlt = 'val' + unit;
}
}
if (nameAlt !== undefined) {
payload[nameAlt + '_' + name] = val;
}
}
}
}
return payload;
}
};
Hi, thanks a million! It seems there is something missing in the code. My log shows:
error 2021-10-31 14:27:26: Failed to call 'Receive' 'onZigbeeEvent' (ReferenceError: precisionRound is not defined
at Object.convert (/opt/zigbee2mqtt/lib/util/externally-loaded.js:23:15)
at /opt/zigbee2mqtt/lib/extension/receive.js:159:41
at Array.forEach (
Does that make any sense to you?
The entire converter files now is as follows:
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const exposes = zigbeeHerdsmanConverters.exposes; const ea = exposes.access; const e = exposes.presets; const fz = zigbeeHerdsmanConverters.fromZigbeeConverters; const tz = zigbeeHerdsmanConverters.toZigbeeConverters;
const ptvo_switch = zigbeeHerdsmanConverters.findByDevice({modelID: 'ptvo.switch'}); fz.legacy = ptvo_switch.meta.tuyaThermostatPreset;
fz.ptvo_switch_analog_input2 = {
cluster: 'genAnalogInput',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const payload = {};
const channel = msg.endpoint.ID;
const name = l${channel}
;
const endpoint = msg.endpoint;
const value_mv = msg.data['presentValue'] * 1000;
const value_scaled = ( (1770- value_mv ) / (1770-835) ) * 100;
payload[name] = precisionRound(value_scaled , 3);
const cluster = 'genLevelCtrl';
if (endpoint && (endpoint.supportsInputCluster(cluster) || endpoint.supportsOutputCluster(cluster))) {
payload['brightness_' + name] = msg.data['presentValue'];
} else if (msg.data.hasOwnProperty('description')) {
const data1 = msg.data['description'];
if (data1) {
const data2 = data1.split(',');
const devid = data2[1];
const unit = data2[0];
if (devid) {
payload['device_' + name] = devid;
}
const valRaw = msg.data['presentValue'];
if (unit) {
let val = precisionRound(valRaw, 1);
const nameLookup = {
'C': 'temperature',
'%': 'humidity',
'm': 'altitude',
'Pa': 'pressure',
'ppm': 'quality',
'psize': 'particle_size',
'V': 'voltage',
'A': 'current',
'Wh': 'energy',
'W': 'power',
'Hz': 'frequency',
'pf': 'power_factor',
'lx': 'illuminance_lux',
};
let nameAlt = '';
if (unit === 'A') {
if (valRaw < 1) {
val = precisionRound(valRaw, 3);
}
}
if (unit.startsWith('mcpm') || unit.startsWith('ncpm')) {
const num = unit.substr(4, 1);
nameAlt = (num === 'A')? unit.substr(0, 4) + '10': unit;
val = precisionRound(valRaw, 2);
} else {
nameAlt = nameLookup[unit];
}
if (nameAlt === undefined) {
const valueIndex = parseInt(unit, 10);
if (! isNaN(valueIndex)) {
nameAlt = 'val' + unit;
}
}
if (nameAlt !== undefined) {
payload[nameAlt + '_' + name] = val;
}
}
}
}
return payload;
} };
const device = { zigbeeModel: ['Bodemvochtigheid'], model: 'Bodemvochtigheid', vendor: 'Custom devices (DiY)', description: 'Configurable firmware', fromZigbee: [fz.ignore_basic_report, fz.ptvo_switch_analog_input2,], toZigbee: [tz.ptvo_switch_trigger, tz.ptvo_switch_analog_input,], exposes: [e.humidity().withAccess(ea.STATE).withValueMin(0.0).withValueMax(3.3).withEndpoint('l1'), ], meta: { multiEndpoint: true,
}, endpoint: (device) => { return { l1: 1, }; }, };
module.exports = device;
I've copied the corresponding function from z2m to the converter.
function precisionRound(number, precision) {
if (typeof precision === 'number') {
const factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
} else if (typeof precision === 'object') {
const thresholds = Object.keys(precision).map(Number).sort((a, b) => b - a);
for (const t of thresholds) {
if (! isNaN(t) && number >= t) {
return precisionRound(number, precision[t]);
}
}
}
return number;
}
fz.ptvo_switch_analog_input2 = {
cluster: 'genAnalogInput',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const payload = {};
const channel = msg.endpoint.ID;
const name = `l${channel}`;
const endpoint = msg.endpoint;
const value_mv = msg.data['presentValue'] * 1000;
const value_scaled = ( (1770- value_mv ) / (1770-835) ) * 100;
payload[name] = precisionRound(value_scaled , 3);
const cluster = 'genLevelCtrl';
if (endpoint && (endpoint.supportsInputCluster(cluster) || endpoint.supportsOutputCluster(cluster))) {
payload['brightness_' + name] = msg.data['presentValue'];
} else if (msg.data.hasOwnProperty('description')) {
const data1 = msg.data['description'];
if (data1) {
const data2 = data1.split(',');
const devid = data2[1];
const unit = data2[0];
if (devid) {
payload['device_' + name] = devid;
}
const valRaw = msg.data['presentValue'];
if (unit) {
let val = precisionRound(valRaw, 1);
const nameLookup = {
'C': 'temperature',
'%': 'humidity',
'm': 'altitude',
'Pa': 'pressure',
'ppm': 'quality',
'psize': 'particle_size',
'V': 'voltage',
'A': 'current',
'Wh': 'energy',
'W': 'power',
'Hz': 'frequency',
'pf': 'power_factor',
'lx': 'illuminance_lux',
};
let nameAlt = '';
if (unit === 'A') {
if (valRaw < 1) {
val = precisionRound(valRaw, 3);
}
}
if (unit.startsWith('mcpm') || unit.startsWith('ncpm')) {
const num = unit.substr(4, 1);
nameAlt = (num === 'A')? unit.substr(0, 4) + '10': unit;
val = precisionRound(valRaw, 2);
} else {
nameAlt = nameLookup[unit];
}
if (nameAlt === undefined) {
const valueIndex = parseInt(unit, 10);
if (! isNaN(valueIndex)) {
nameAlt = 'val' + unit;
}
}
if (nameAlt !== undefined) {
payload[nameAlt + '_' + name] = val;
}
}
}
}
return payload;
}
};
Hi, cheers for that! Now the log shows both the calculated and the measured value, but the only value I get in Domoticz is the measured one. I figured I have to tell it to send the other value, but after trying almost everthing which made some sort of sense to me I can't get it to work..
Log shows:
info 2021-11-01 13:29:09: MQTT publish: topic 'zigbee2mqtt/0x00124b001adf71f2', payload '{"l1":113.679,"linkquality":84,"voltage_l1":703}'
I would need the 113.679 but 703 is being sent..
Funny, I've just managed to get this working a couple of days ago. Turned out I had to expose both humidity and voltage to get the right value in Domoticz. Working like a charm now, thanks!
Hi,
First of all, thanks for your fantastic work! I've been struggeling with an issue for weeks now, so therefore my question. I've build a DIY soil moisture sensor connected to a CC2530 flashed with the ptvo firmware . The analog input is connected to p01 and the value is reported to my Domoticz installation through zigbee2mqtt. The only thing is that I can't succeed in converting the reading to a percentage, saying value x is 0% en y is 100%. Spent a lot of time googling and trying but nothing works or stops zigbee2mqtt from starting. My external converter looks like this:
`const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const exposes = zigbeeHerdsmanConverters.exposes; const ea = exposes.access; const e = exposes.presets; const fz = zigbeeHerdsmanConverters.fromZigbeeConverters; const tz = zigbeeHerdsmanConverters.toZigbeeConverters;
const ptvo_switch = zigbeeHerdsmanConverters.findByDevice({modelID: 'ptvo.switch'}); fz.legacy = ptvo_switch.meta.tuyaThermostatPreset;
const device = { zigbeeModel: ['Bodemvochtigheid'], model: 'Bodemvochtigheid', vendor: 'Custom devices (DiY)', description: 'Configurable firmware', fromZigbee: [fz.ignore_basic_report, fz.ptvo_switch_analog_input,], toZigbee: [tz.ptvo_switch_trigger, tz.ptvo_switch_analog_input,], exposes: [e.voltage().withAccess(ea.STATE).withValueMin(0.0).withValueMax(3.3).withEndpoint('l1'), ], meta: { multiEndpoint: true,
};
module.exports = device; `
Can anyone help me in my quest to solve this...? Thanks in advance!