Sensor arcón congelador ZT08 e integración en Home Assistant

Hace unas semanas vi un termómetro zigbee con una sonda externa que me podia ir bien para el arcón congelador que tengo , el enlace de Aliexpres es este

Lo elegí porque el que tenia antes a partir de -20ºC tenia problemas y no daba lectura , el mínimo teórico de este dispositivo son -40ºC

Se puede colocar de diferentes maneras , desde magnética , adhesiva y con ventosa , al final me decante por la adhesiva en la tapa de las baterías.

Va alimentado por tres pilas AAA

Al alimentarlo ya nos muestra la temperatura local , la de las sonda , la hora y el nivel de bateria , pero las temperaturas en grados Fahrenheit

El siguiente paso es integrarlo en zigbee2mqtt , la información de este dispositivo será la siguiente.

Nada mas emparejarlo nos dice que no esta soportado , y nos devuelve este identificador “_TZE284_hodyryli

Para disponer de el tendremos que usar un converter , aquí encontre uno que funcionaba , el código seria el siguiente :

const tuya = require('zigbee-herdsman-converters/lib/tuya');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const e = exposes.presets;
const ea = exposes.access;

const fzLocal = {
    tuya_weather_station: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandActiveStatusReport', 'commandMcuSyncTime'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            
            // Time synchro
            if (msg.type === 'commandMcuSyncTime') {
                (async () => {
                    try {
                        const endpoint = msg.endpoint;
                        const now = new Date();
                        
                        const utcTime = Math.round(now.getTime() / 1000);
                        const localTime = utcTime - (now.getTimezoneOffset() * 60);
                        
                        const utcArray = [
                            (utcTime >> 24) & 0xFF,
                            (utcTime >> 16) & 0xFF,
                            (utcTime >> 8) & 0xFF,
                            utcTime & 0xFF,
                        ];
                        
                        const localArray = [
                            (localTime >> 24) & 0xFF,
                            (localTime >> 16) & 0xFF,
                            (localTime >> 8) & 0xFF,
                            localTime & 0xFF,
                        ];
                        
                        const payload = {
                            payloadSize: 8,
                            payload: [...utcArray, ...localArray],
                        };
                        
                        await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {disableDefaultResponse: true});
                        
                        // set 24h time format
                        await new Promise(resolve => setTimeout(resolve, 500));
                        try {
                            await tuya.sendDataPointBool(endpoint, 17, false);
                        } catch (e) {
                            // Ignore if DP 17 don't exist
                        }
                        
                    } catch (error) {
                        // Ignore time synchro errors
                    }
                })();
                
                return {};
            }
            
            if (!msg.data || !msg.data.dpValues) return {};
            
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const data = dpValue.data;
                
                let value;
                // temperature
                if (dpValue.datatype === 2) {
                    // Reads the data as an unsigned big-endian 32-bit integer (Negative numbers will be wrong, eg. 429496724.6)
                    // value = data.readUInt32BE(0);
                    // Reads the data as a signed big-endian 32-bit integer (Negative numbers will be correct)
                    value = data.readInt32BE(0);
                } else if (dpValue.datatype === 4) {
                    value = data[0];
                } else {
                    value = data[0];
                }
                
                switch (dp) {
                    // temperature
                    case 1:
                        result.temperature = value / 10;
                        break;
                    // humidity
                    case 2:
                        result.humidity = value;
                        break;
                    // battery_state
                    case 3:
                        result.battery_state = value;
                        result.battery = value === 0 ? 10 : (value === 1 ? 50 : 100);
                        result.battery_low = value === 0;
                        break;
                    // time_format
                    case 17:
                        result.time_format = value ? '12h' : '24h';
                        break;
                    // temperature_external
                    case 38:
                        result.temperature_external = value / 10;
                        break;
                }
            }
            
            return result;
        },
    },
};

const definition = {
    fingerprint: tuya.fingerprint('TS0601', ['_TZE284_hodyryli']),
    model: 'TS0601_weather_station',
    vendor: 'TuYa',
    description: 'Weather station with clock, internal/external temperature and humidity',
    fromZigbee: [fzLocal.tuya_weather_station],
    toZigbee: [tuya.tz.datapoints],
    configure: tuya.configureMagicPacket,
    exposes: [
        e.temperature().withDescription('Internal temperature'),
        e.humidity().withDescription('Internal humidity'),
        e.numeric('temperature_external', ea.STATE)
            .withUnit('°C')
            .withDescription('External temperature sensor'),
        e.battery()
            .withDescription('Battery level (10%=low, 50%=medium, 100%=full)'),
        e.battery_low()
            .withDescription('Battery low warning'),
        e.enum('battery_state', ea.STATE, [0, 1, 2])
            .withDescription('Raw battery state (0=low, 1=medium, 2=full)'),
        e.enum('time_format', ea.STATE, ['12h', '24h'])
            .withDescription('Clock time format'),
    ],
    meta: {
        tuyaDatapoints: [
            [1, 'temperature', tuya.valueConverter.divideBy10],
            [2, 'humidity', tuya.valueConverter.raw],
            [3, 'battery_state', tuya.valueConverter.raw],
            [17, 'time_format', tuya.valueConverter.onOff],
            [38, 'temperature_external', tuya.valueConverter.divideBy10],
        ],
    },
};

module.exports = definition;

Crearemos el fichero TS0601.js y copiamos el código en el

paramos el docker

en configuration.yaml añadimos el fichero del converter

groups: {}
external_converters:
  - TS0601.js
  - TS0202.js

Arrancamos el docker y ya nos aparece el nuevo dispositivo

Ya podemos ver como empieza a exponer los datos

Físicamente una vez colocado quedaria así , con los grados en Fahrenheit 🙁

  '0xa4c138f9601e97ca':
    friendly_name: '0xa4c138f9601e97ca'
    temperature_calibration: 0
    temperature_precision: 1
    humidity_calibration: 0
    humidity_precision: 0

Crearemos nuestros sensores

  ### TERMOMETRO ARCON CONGELADOR
  
    - state_topic: "zigbee2mqtt/temperatura_congelador"
      availability_topic: "zigbee2mqtt/bridge/state"
      unit_of_measurement: "°C"
      device_class: "temperature"
      value_template: "{{ value_json.temperature }}"
      name: "temperatura_lavadero"  
      
    - state_topic: "zigbee2mqtt/temperatura_congelador"
      availability_topic: "zigbee2mqtt/bridge/state"
      unit_of_measurement: "°C"
      device_class: "temperature"
      value_template: "{{ value_json.temperature_external }}"
      name: "temperatura_congelador_temperatura"  
  
    - state_topic: "zigbee2mqtt/temperatura_congelador"
      availability_topic: "zigbee2mqtt/bridge/state"
      unit_of_measurement: "%"
      device_class: "humidity"
      value_template: "{{ value_json.humidity }}"
      name: "temperatura_congelador_humedad"  
  
    - state_topic: "zigbee2mqtt/temperatura_congelador"
      availability_topic: "zigbee2mqtt/bridge/state"
      unit_of_measurement: "%"
      icon: "mdi:battery"
      device_class: "battery"
      value_template: "{{ value_json.battery }}"
    
      expire_after: 86400
      force_update: true
      name: "temperatura_congelador_bateria"  
  
    - state_topic: "zigbee2mqtt/temperatura_congelador"
      availability_topic: "zigbee2mqtt/bridge/state"
      icon: "mdi:signal"
      unit_of_measurement: "lqi"
      value_template: "{{ value_json.linkquality }}"    
      name: "temperatura_congelador_estado"  
      
    - state_topic: "zigbee2mqtt/temperatura_congelador"
      availability_topic: "zigbee2mqtt/bridge/state"
      icon: "mdi:calendar-clock"
      value_template: "{{ value_json.last_seen }}"
      name: "temperatura_congelador_ultima_conexion" 

Y después de reiniciar veremos como va guardando los diferentes valores

Y con esto y un bizcocho …………

BONUS : Me tocaba bastante los webs ver los grados en Fahrenheit por lo que mire a ver como ponerlos en Celsius

Para ello cambiaremos el código del converter por este

const tuya = require('zigbee-herdsman-converters/lib/tuya');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const e = exposes.presets;
const ea = exposes.access;

const fzLocal = {
    tuya_weather_station: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandActiveStatusReport', 'commandMcuSyncTime'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            
            // Time synchro
            if (msg.type === 'commandMcuSyncTime') {
                (async () => {
                    try {
                        const endpoint = msg.endpoint;
                        const now = new Date();
                        
                        const utcTime = Math.round(now.getTime() / 1000);
                        const localTime = utcTime - (now.getTimezoneOffset() * 60);
                        
                        const utcArray = [
                            (utcTime >> 24) & 0xFF,
                            (utcTime >> 16) & 0xFF,
                            (utcTime >> 8) & 0xFF,
                            utcTime & 0xFF,
                        ];
                        
                        const localArray = [
                            (localTime >> 24) & 0xFF,
                            (localTime >> 16) & 0xFF,
                            (localTime >> 8) & 0xFF,
                            localTime & 0xFF,
                        ];
                        
                        const payload = {
                            payloadSize: 8,
                            payload: [...utcArray, ...localArray],
                        };
                        
                        await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {disableDefaultResponse: true});
                        
                        // set 24h time format
                        await new Promise(resolve => setTimeout(resolve, 500));
                        try {
                            await tuya.sendDataPointBool(endpoint, 17, false);
                        } catch (e) {
                            // Ignore if DP 17 don't exist
                        }
                        
                    } catch (error) {
                        // Ignore time synchro errors
                    }
                })();
                
                return {};
            }
            
            if (!msg.data || !msg.data.dpValues) return {};
            
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const data = dpValue.data;
                
                let value;
                // temperature
                if (dpValue.datatype === 2) {
                    // Reads the data as an unsigned big-endian 32-bit integer (Negative numbers will be wrong, eg. 429496724.6)
                    // value = data.readUInt32BE(0);
                    // Reads the data as a signed big-endian 32-bit integer (Negative numbers will be correct)
                    value = data.readInt32BE(0);
                } else if (dpValue.datatype === 4) {
                    value = data[0];
                } else {
                    value = data[0];
                }
                
                switch (dp) {
                    // temperature
                    case 1:
                        result.temperature = value / 10;
                        break;
                    // humidity
                    case 2:
                        result.humidity = value;
                        break;
                    // battery_state
                    case 3:
                        result.battery_state = value;
                        result.battery = value === 0 ? 10 : (value === 1 ? 50 : 100);
                        result.battery_low = value === 0;
                        break;
                    // temperature_unit (LCD display)
                    case 9:
                        result.temperature_unit = value === 0 ? 'celsius' : 'fahrenheit';
                        break;
                    // time_format
                    case 17:
                        result.time_format = value ? '12h' : '24h';
                        break;
                    // temperature_external
                    case 38:
                        result.temperature_external = value / 10;
                        break;
                }
            }
            
            return result;
        },
    },
};

const definition = {
    fingerprint: tuya.fingerprint('TS0601', ['_TZE284_hodyryli']),
    model: 'TS0601_weather_station',
    vendor: 'TuYa',
    description: 'Weather station with clock, internal/external temperature and humidity',
    fromZigbee: [fzLocal.tuya_weather_station],
    toZigbee: [tuya.tz.datapoints],
    configure: tuya.configureMagicPacket,
    exposes: [
        e.temperature().withDescription('Internal temperature'),
        e.humidity().withDescription('Internal humidity'),
        e.numeric('temperature_external', ea.STATE)
            .withUnit('°C')
            .withDescription('External temperature sensor'),
        e.battery()
            .withDescription('Battery level (10%=low, 50%=medium, 100%=full)'),
        e.battery_low()
            .withDescription('Battery low warning'),
        e.enum('battery_state', ea.STATE, [0, 1, 2])
            .withDescription('Raw battery state (0=low, 1=medium, 2=full)'),
        e.enum('temperature_unit', ea.STATE_SET, ['celsius', 'fahrenheit'])
            .withDescription('Temperature unit displayed on LCD screen'),
        e.enum('time_format', ea.STATE, ['12h', '24h'])
            .withDescription('Clock time format'),
    ],
    meta: {
        tuyaDatapoints: [
            [1, 'temperature', tuya.valueConverter.divideBy10],
            [2, 'humidity', tuya.valueConverter.raw],
            [3, 'battery_state', tuya.valueConverter.raw],
            [9, 'temperature_unit', tuya.valueConverter.temperatureUnitEnum],
            [17, 'time_format', tuya.valueConverter.onOff],
            [38, 'temperature_external', tuya.valueConverter.divideBy10],
        ],
    },
};

module.exports = definition;

Por defecto lo tendremos asi

Al consultar los ajustes nos aparece un nuevo selector Celsius / Fahrenheit

Cambiamos a Celsius y pulsamos varias veces para que se actualice

Y tachannnnnnnnnnnnnn ya aparece en Celsius