Ton fichier me semble pas bon. Il manque l’appel aux constantes.
Voici un fichier que j’ai en « external converter » :
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const e = exposes.presets;
const ea = exposes.access;
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const tzLocal = {
tamper_alarm_switch: {
key: ['tamper_alarm_switch'],
convertSet: async (entity, key, value, meta) => {
const lookup = {'off': false, 'on': true };
value = value.toLowerCase();
const pState = lookup [value];
await tuya.sendDataPointBool(entity, 101, pState);
return {state: {tamper_alarm_switch: value}};
}
},
alarm_mode: {
key: ['alarm_mode'],
convertSet: async (entity, key, value, meta) => {
value = value.toLowerCase();
await tuya.sendDataPointEnum(entity, 102, {'alarm_sound': 0, 'alarm_light': 1, 'alarm_sound_light': 2 }[value]);
return {state: {alarm_mode: value}};
}
},
alarm_melody: {
key: ['alarm_melody'],
convertSet: async (entity, key, value, meta) => {
value = value.toLowerCase();
await tuya.sendDataPointEnum(entity, 21, {'melody1': 0, 'melody2': 1, 'melody3': 2}[value]);
return {state: {alarm_melody: value}};
}
},
battery: {
key: ['battery'],
convertGet: async (entity, key, meta) => {
const result = await tuya.sendDataPoint(entity, 15);
const batteryData = result.dpValues.find(dpValue => dpValue.dp === 15);
if (batteryData) {
const batteryLevel = batteryData.data.data[3];
return batteryLevel;
}
return null;
},
},
}
const definition = {
// Since a lot of TuYa devices use the same modelID, but use different datapoints
// it's necessary to provide a fingerprint instead of a zigbeeModel
fingerprint: [
{
// The model ID from: Device with modelID 'TS0601' is not supported
// You may need to add \u0000 at the end of the name in some cases
modelID: 'TS0601',
// The manufacturer name from: Device with modelID 'TS0601' is not supported.
manufacturerName: '_TZE200_nlrfgpny',
},
],
model: 'NAS-AB06B2',
vendor: 'Neo',
description: 'Outdoor solar alarm',
fromZigbee: [tuya.fz.datapoints],
toZigbee: [tuya.tz.datapoints, tzLocal.tamper_alarm_switch,tzLocal.alarm_mode,tzLocal.alarm_melody],
onEvent: tuya.onEventSetTime, // Add this if you are getting no converter for 'commandMcuSyncTime'
configure: tuya.configureMagicPacket,
exposes: [
exposes.enum('alarm_state', ea.STATE, ['alarm_sound', 'alarm_light', 'alarm_sound_light', 'normal']).withDescription('Alarm status'),
exposes.binary('alarm_switch', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable alarm'),
exposes.binary('tamper_alarm_switch', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable tamper alarm'),
exposes.binary('tamper_alarm', ea.STATE, 'ON', 'OFF').withDescription('Indicates whether the device is tampered'),
exposes.enum('alarm_melody', ea.STATE_SET, ['melody1', 'melody2', 'melody3']).withDescription('Alarm sound effect'),
exposes.enum('alarm_mode', ea.STATE_SET, ['alarm_sound', 'alarm_light', 'alarm_sound_light']).withDescription('Alarm mode'),
exposes.numeric('alarm_time', ea.STATE_SET).withValueMin(1).withValueMax(60).withValueStep(1).withUnit('min').withDescription('Alarm duration in minutes'),
exposes.binary('charge_state', ea.STATE, 'Charging', 'Not Charging').withDescription('Charging status'),
// exposes.numeric('battery', ea.STATE).withDescription('Battery percentage'),
exposes.numeric('battpercentage', ea.STATE).withUnit('%').withDescription('Remaining battery in % (can take up to 24 hours before reported)'),
exposes.binary('battery_low', ea.STATE, true, false).withDescription('Indicates if the battery of this device is almost empty'),
],
meta: {
// All datapoints go in here
tuyaDatapoints: [
[1, 'alarm_state', tuya.valueConverterBasic.lookup({'alarm_sound': 0, 'alarm_light': 1, 'alarm_sound_light': 2,'normal': 3})],
[13, 'alarm_switch', tuya.valueConverter.onOff],
[101, 'tamper_alarm_switch', tuya.valueConverter.onOff],
[20, 'tamper_alarm', tuya.valueConverter.onOff],
[21, 'alarm_melody', tuya.valueConverterBasic.lookup({'melody1': 0, 'melody2': 1, 'melody3': 2})],
[102, 'alarm_mode', tuya.valueConverterBasic.lookup({'alarm_sound': 0, 'alarm_light': 1, 'alarm_sound_light': 2})],
[7, 'alarm_time', tuya.valueConverter.raw],
[6, 'charge_state', tuya.valueConverter.onOff],
[15, 'battpercentage', tuya.valueConverter.raw]
],
},
};
module.exports = definition;
Tu n’as pas les premières lignes (commençant par « const ») et qui sont indispensables.
Le convertisseur en .text posté sur le github me parait incorrect :
const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const utils = require('zigbee-herdsman-converters/lib/utils');
const ota = require('zigbee-herdsman-converters/lib/ota');
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;
const ea = exposes.access;
const tuyaExposes = {
powerOnBehavior: exposes.enum('power_on_behavior', ea.STATE_SET, ['off', 'on', 'previous'])
.withDescription('Controls the behavior when the device is powered on'),
lightType: exposes.enum('light_type', ea.STATE_SET, ['led', 'incandescent', 'halogen'])
.withDescription('Type of light attached to the device'),
lightBrightnessWithMinMax: e.light_brightness().withMinBrightness().withMaxBrightness().setAccess(
'state', ea.STATE_SET).setAccess('brightness', ea.STATE_SET).setAccess(
'min_brightness', ea.STATE_SET).setAccess('max_brightness', ea.STATE_SET),
countdown: exposes.numeric('countdown', ea.STATE_SET).withValueMin(0).withValueMax(43200).withValueStep(1).withUnit('s')
.withDescription('Countdown to turn device off after a certain time')
}
const configureMagicPacket = async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(1);
await endpoint.read('genBasic', ['manufacturerName', 'zclVersion', 'appVersion', 'modelId', 'powerSource', 0xfffe]);
};
const fingerprint = (modelID, manufacturerNames) => {
return manufacturerNames.map((manufacturerName) => {
return {modelID, manufacturerName};
})
}
const valueConverterBasic = {
lookup: (map) => {
return {
to: (v) => {
if (map[v] === undefined) throw new Error(`Value '${v}' is not allowed, expected one of ${Object.keys(map)}`)
return map[v];
},
from: (v) => {
const value = Object.entries(map).find((i) => i[1] === v);
if (!value) throw new Error(`Value '${v}' is not allowed, expected one of ${Object.values(map)}`)
return value[0];
}
}
},
scale: (min1, max1, min2, max2) => {
return {to: (v) => utils.mapNumberRange(v, min1, max1, min2, max2), from: (v) => utils.mapNumberRange(v, min2, max2, min1, max1)};
},
raw: {to: (v) => v, from: (v) => v},
}
const valueConverter = {
onOff: valueConverterBasic.lookup({'ON': true, 'OFF': false}),
powerOnBehavior: valueConverterBasic.lookup({'off': 0, 'on': 1, 'previous': 2}),
lightType: valueConverterBasic.lookup({'led': 0, 'incandescent': 1, 'halogen': 2}),
scale0_254to0_1000: valueConverterBasic.scale(0, 254, 0, 1000),
scale1_255to0_1000: valueConverterBasic.scale(1, 255, 0, 1000),
}
const tzDataPoints = {
key: ['state', 'brightness', 'min_brightness', 'max_brightness'],
convertSet: async (entity, key, value, meta) => {
// A set converter is only called once; therefore we need to loop
const state = {};
if (!meta.mapped.meta || !meta.mapped.meta.datapoints) throw new Error('No datapoints map defined');
const datapoints = meta.mapped.meta.datapoints
for (const [key, value] of Object.entries(meta.message)) {
const convertedKey = meta.mapped.meta.multiEndpoint ? `${key}_${meta.endpoint_name}` : key;
const dpEntry = datapoints.find(d => d[1] === convertedKey);
if (!dpEntry || !dpEntry[1]) {
throw new Error(`No datapoint defined for '${key}'`);
}
const dpId = dpEntry[0];
const convertedValue = dpEntry[2].to(value);
await tuya.sendDataPointValue(entity, dpId, convertedValue, 'dataRequest', 1);
state[convertedKey] = value;
}
return {state};
}
};
const fzDataPoints = {
cluster: 'manuSpecificTuya',
type: ['commandDataResponse', 'commandDataReport'],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (!model.meta || !model.meta.datapoints) throw new Error('No datapoints map defined');
const datapoints = model.meta.datapoints
for (const dpValue of msg.data.dpValues) {
const dpId = dpValue.dp;
const dpEntry = datapoints.find(d => d[0] === dpId);
if (dpEntry) {
const value = tuya.getDataValue(dpValue);
result[dpEntry[1]] = dpEntry[2].from(value);
} else {
meta.logger.warn(`Datapoint ${dpId} not defined for '${meta.device.manufacturerName}' with data ${JSON.stringify(dpValue)}`);
}
}
return result;
}
};
const definition = {
fingerprint: tuya.fingerprint('TS0601', ['_TZE204_kyhbrfyl']),
model: 'RTX PRO',
vendor: 'TuYa',
description: 'Human presence sensor',
fromZigbee: [tuya.fz.datapoints],
toZigbee: [tuya.tz.datapoints],
onEvent: tuya.onEventSetTime,
configure: tuya.configureMagicPacket,
exposes: [
e.presence(),
e.enum('approach_distance', ea.STATE, ['none', 'small_movement', 'large_movement'])
.withDescription('traffic of movement from the point of view of the radar'),
e.numeric('detection_delay', ea.STATE_SET).withValueMin(0).withValueMax(600).withValueStep(1)
.withDescription('detection delay'),
e.numeric('detection_range', ea.STATE_SET).withValueMin(150).withValueMax(600).withValueStep(10)
.withDescription('far sensitivity of the radar'),
e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(7).withValueStep(1)
.withDescription('Sensitivity of the radar'),
e.numeric('motion_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(7).withValueStep(1)
.withDescription('Motionless sensitivity of the radar'),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e.numeric('battery_level', ea.STATE).withDescription('battery level').withUnit('%'),
],
meta: {
tuyaDatapoints: [
[1, 'presence', tuya.valueConverter.trueFalse1],
[11, 'approach_distance', tuya.valueConverterBasic.lookup({'none': 0, 'small_movement': 1, 'large_movement': 2})],
[12, 'detection_delay', tuya.valueConverter.raw],
[13, 'detection_range', tuya.valueConverter.raw],
[15, 'radar_sensitivity', tuya.valueConverter.raw],
[16, 'motion_sensitivity', tuya.valueConverter.raw],
[19, "target_distance", tuya.valueConverter.divideBy100],
[101, 'battery_level', tuya.valueConverter.raw],
],
},
};
module.exports = definition;
Supprime ton fichier .js et crée en un nouveau, vierge, puis édite le et colle les lignes ci-dessous dedans. Enregistre le tout et redémarre le système.
Tu as un lien vers le modèle exacte de ton détecteur (il y a des appels qui ne correspondent à rien pour un détecteur de présence dans le convertisseur posté en .text).
Utilises les lignes ci-dessous à coller dans ton fichier .js :
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;
const ea = exposes.access;
const definition = {
fingerprint: tuya.fingerprint('TS0601', ['_TZE204_kyhbrfyl']),
model: 'RTX PRO',
vendor: 'TuYa',
description: 'Human presence sensor',
fromZigbee: [tuya.fz.datapoints],
toZigbee: [tuya.tz.datapoints],
onEvent: console.log,
// configure: tuya.configureMagicPacket,
exposes: [
e.presence(),
exposes.enum('movement_type', ea.STATE, ['none', 'small', 'large'])
.withDescription('traffic of movement from the point of view of the radar'),
exposes.numeric('detection_delay', ea.STATE_SET).withValueMin(0).withValueMax(600).withValueStep(1).withUnit('s')
.withDescription('detection delay'),
exposes.numeric('detection_range', ea.STATE_SET).withValueMin(150).withValueMax(600).withValueStep(10)
.withDescription('far sensitivity of the radar'),
exposes.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(7).withValueStep(1)
.withDescription('Sensitivity of the radar'),
exposes.numeric('motion_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(7).withValueStep(1)
.withDescription('Motionless sensitivity of the radar'),
exposes.numeric('current_distance', ea.STATE).withDescription('Distance to target').withUnit('cm'),
exposes.numeric('battery_level', ea.STATE).withDescription('battery level').withUnit('%'),
],
meta: {
tuyaDatapoints: [
[1, 'presence',tuya.valueConverter.trueFalse1],
[11, 'movement_type', tuya.valueConverterBasic.lookup({'none': 0, 'small': 1, 'large': 2})],
[12, 'detection_delay', tuya.valueConverter.raw],
[13, 'detection_range', tuya.valueConverter.raw],
[15, 'radar_sensitivity', tuya.valueConverter.raw],
[16, 'motion_sensitivity', tuya.valueConverter.raw],
[19, 'current_distance', tuya.valueConverter.raw],
[101, 'battery_level', tuya.valueConverter.raw],
],
},
};
module.exports = definition;