Koenkk/zigbee-herdsman-converters

In the Z2M Edge version (16.15.1 or 16.16?) BTH-RA gives errors in configuration

bullmastiffo opened this issue ยท 13 comments

Zigbee Model
RBSH-TRV0-ZB-EU
Zigbee Manufacturer
BOSCH
Description
Radiator thermostat II
Firmware build date
20220627
Firmware version
3.02.05

In the Edge version logs:

Zigbee2MQTT:info  2023-12-22 10:45:01: Configuring 'Bosch Valve Living Room'
2023-12-22T09:45:01.984Z zigbee-herdsman:controller:endpoint Bind 0x18fc260000a197e4/1 genPowerCfg from '0xbc026efffe228982/1'
2023-12-22T09:45:01.984Z zigbee-herdsman:adapter:ezsp:driv ZDO Bind_req params: {"sourceEui":{"_value":{"type":"Buffer","data":[24,252,38,0,0,161,151,228]}},"sourceEp":1,"clusterId":1,"destAddr":{"addrmode":3,"ieee":{"_value":{"type":"Buffer","data":[188,2,110,255,254,34,137,130]}},"endpoint":1}}
2023-12-22T09:45:01.986Z zigbee-herdsman:adapter:ezsp:ezsp ==> setExtendedTimeout: {"remoteEui64":{"_value":{"type":"Buffer","data":[188,2,110,255,254,34,137,130]}},"extendedTimeout":true}
2023-12-22T09:45:01.987Z zigbee-herdsman:adapter:ezsp:ezsp ==> {"_cls_":"setExtendedTimeout","_id_":126,"_isRequest_":true,"remoteEui64":{"_value":{"type":"Buffer","data":[188,2,110,255,254,34,137,130]}},"extendedTimeout":true}

...

2023-12-22T09:45:17.187Z zigbee-herdsman:controller:endpoint Bind 0x18fc260000a197e4/1 hvacThermostat from '0xbc026efffe228982/1' failed ({"address":42360,"clusterId":32801,"sequence":29} after 10000ms)
Zigbee2MQTT:error 2023-12-22 10:45:17: Failed to configure 'Bosch Valve Living Room', attempt 3 (Error: Bind 0x18fc260000a197e4/1 hvacThermostat from '0xbc026efffe228982/1' failed ({"address":42360,"clusterId":32801,"sequence":29} after 10000ms)
    at Timeout._onTimeout (/app/node_modules/zigbee-herdsman/src/utils/waitress.ts:64:35)
    at listOnTimeout (node:internal/timers:569:17)
    at processTimers (node:internal/timers:512:7))
2023-12-22T09:45:17.457Z zigbee-herdsman:controller:endpoint Bind 0x18fc2600000f3902/1 hvacThermostat from '0xbc026efffe228982/1' failed ({"address":14884,"clusterId":32801,"sequence":30} after 10000ms)
Zigbee2MQTT:error 2023-12-22 10:45:17: Failed to configure 'Bosch Valve Common Bathroom', attempt 3 (Error: Bind 0x18fc2600000f3902/1 hvacThermostat from '0xbc026efffe228982/1' failed ({"address":14884,"clusterId":32801,"sequence":30} after 10000ms)
    at Timeout._onTimeout (/app/node_modules/zigbee-herdsman/src/utils/waitress.ts:64:35)
    at listOnTimeout (node:internal/timers:569:17)
    at processTimers (node:internal/timers:512:7))

here's the full log
fullConfigureLogBosch.txt

I thought it could be related to #6742 , but the stable version has the same issue
StableVersionLogBosch.txt

@DerDreschner do you see such config issues by the way?

@bullmastiffo : No, you have to open an issue. There is no automated tracking of any problems (i.e. sentry, etc.). Sorry, I misunderstood you with my brain in a hurry. I've didn't saw issues like you myself. Just once at the end of an firmware update attempt, but it wasn't a timeout.

Without looking at the logs in details as I'm on the go right now: Is the valve calibrated and displaying the current temperature? Or does it look like in the right side of this picture? https://www.testventure.de/wp-content/uploads/2022/11/Bosch-Heizkoerperthermostat-Montage.jpg

@bullmastiffo : Ohh, and did you try to upgrade the firmware and this happened at the end? Or did you try to pair a new valve?

@bullmastiffo : Do you have more logfiles? It looks like you tried to update the firmware and something didn't went like it should. Would be great to see what happened before the configuration phase.

I've been using the valve for quite some time (maybe the issue was present before). It's calibrated and seems to be working fine. Also I've updated the valve some time ago (around 2 months, I don't recall any issues while updating). I've noticed this config issue while switching on Z2M dev branch today. It tried to configure device with the new branch. So no update was involved
Then I switched back to stable and tried to reconfigure and had the same issue. Maybe it's something with the device itself.. I could try maybe resetting and readding to the network first.

@bullmastiffo : That's strange because the thermostat is sending upgradeEndRequest messages. That's why I thought you tried to update the firmware. I noticed the firmware information in Z2M itself is also inconsistent. The update you installed ~2 months ago isn't the version you mentioned in the first post. Looks like something went wrong at the end like it does for some folks, too (see Koenkk/zigbee2mqtt#17740).

Did you already removed the batteries and see if the error persists? Resetting the device itself may be a good idea, too.

@bullmastiffo : Did the reset resolved the issue? If not, I can try to provide you an external converter to respond to the upgradeEndRequest the thermostat sends. This should solve the issue if nothing else helped before.

By the way, many thanks for the logfiles! It's the first time I've got some with issues like that. Have an idea what it could be now and opened a PR for it (#6761).

Merry Christmas anyway!

@bullmastiffo : Did the reset resolved the issue? If not, I can try to provide you an external converter to respond to the upgradeEndRequest the thermostat sends. This should solve the issue if nothing else helped before.

By the way, many thanks for the logfiles! It's the first time I've got some with issues like that. Have an idea what it could be now and opened a PR for it (#6761).

Merry Christmas anyway!

Hey Merry Christmas! :) Thanks a lot for your responsiveness and willingness to help! :)

So, reset didn't help, it made it worse. After reset and removing from z2m, it was not able even to interview the device, same issue, the packet with UpdateResponse kept coming up.
before the reset I've tried to send manually via DevTools upgradeEndResponse like in ota\common.ts to genOta cluster (25), but I think I couldn't send it properly, it allowed to send command 6 but not 7. saying there's no command 7 with this payload.

{
        "manufacturerCode": 4617,
        "imageType": 12298,
        "fileVersion": 889525524,
        "imageSize": null,
        "currentTime": 0, 
        "upgradeTime": 1
    }

I've decided that device needs a fresh start, but I have only one dongle, so I've stopped z2m, then installed zha, added device there, manually updated it there, the update worked. Now I am back to z2m, but all devices are gone. I think zha messed the dongle config. I would expect z2m to restore dongle settings and just get back the network, it's not the case. I will try one more time to get the network back and otherwise will restore HA from backup, I hope that will help restore the network config for zigbee.

I still have 2 other Bosch TRVs with the same issue after update, I'd be happy to try external converter on those once I get the network back (hopefully).

@bullmastiffo : Even worse? Ugh, that's rough! Sorry to hear that. That shouldn't happen. Especially not within winter time. Hope the WAF doesn't suffer because of the issue?

Could you please place the collapsed code at the end of the comment as bosch.js into the zigbee2mqtt folder (e.g. with "file editor" add-on for HA) and then edit the configuration.yaml to include the newly created file as external converter?

external_converters:
  - bosch.js

Please keep debug logging enabled and post the result here. Don't do any other OTA updates with this external converter enabled. It's just to recover your devices from the failed update. It includes the latest changes I developed, except the remote valve calibration as that requires changes within zigbee-herdsman. If you use Z2M Edge and like to use it, you would need to comment out the thrown "Not implemented" error and uncomment the command below it.

I'm looking forward to hear from you!

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

// Radiator Thermostat II
const manufacturerOptions = {manufacturerCode: herdsman.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH};

// Radiator Thermostat II
const operatingModes = {
    'automatic': 0,
    'manual': 1,
    'pause': 5,
};

// Radiator Thermostat II
const stateOffOn = {
    'OFF': 0,
    'ON': 1,
};

// Radiator Thermostat II
const displayOrientation = {
    'normal': 0,
    'flipped': 1,
};

// Radiator Thermostat II
const displayedTemperature = {
    'target': 0,
    'measured': 1,
};

const setpointSource = {
    'manual': 0,
    'schedule': 1,
    'externally': 2,
};

const adaptationStatus = {
    'none': 0,
    'ready_to_calibrate': 1,
    'calibration_in_progress': 2,
    'error': 3,
    'success': 4,
};

// Radiator Thermostat II
const tzLocal = {
    bosch_thermostat: {
        key: ['window_open', 'boost', 'system_mode', 'pi_heating_demand', 'remote_temperature', 'valve_adapt_process'],
        convertSet: async (entity, key, value, meta) => {
            if (key === 'window_open') {
                value = value.toUpperCase();
                utils.validateValue(value, Object.keys(stateOffOn));
                const index = stateOffOn[value];
                await entity.write('hvacThermostat', {0x4042: {value: index, type: herdsman.Zcl.DataType.enum8}}, manufacturerOptions);
                return {state: {window_open: value}};
            }
            if (key === 'boost') {
                value = value.toUpperCase();
                utils.validateValue(value, Object.keys(stateOffOn));
                const index = stateOffOn[value];
                await entity.write('hvacThermostat', {0x4043: {value: index, type: herdsman.Zcl.DataType.enum8}}, manufacturerOptions);
                return {state: {boost: value}};
            }
            if (key === 'system_mode') {
                // Map system_mode (Off/Auto/Heat) to Bosch operating mode
                utils.assertString(value, key);
                value = value.toLowerCase();

                let opMode = operatingModes.manual; // OperatingMode 1 = Manual (Default)

                if (value=='off') {
                    opMode = operatingModes.pause; // OperatingMode 5 = Pause
                } else if (value == 'auto') {
                    opMode = operatingModes.automatic; // OperatingMode 0 = Automatic
                }
                await entity.write('hvacThermostat', {0x4007: {value: opMode, type: herdsman.Zcl.DataType.enum8}}, manufacturerOptions);
                return {state: {system_mode: value}};
            }
            if (key === 'pi_heating_demand') {
                await entity.write('hvacThermostat',
                    {0x4020: {value: value, type: herdsman.Zcl.DataType.enum8}},
                    manufacturerOptions);
                return {state: {pi_heating_demand: value}};
            }
            if (key === 'remote_temperature') {
                let temperature = utils.toNumber(value, key);
                temperature = utils.precisionRound(temperature, 1);
                const convertedTemperature = utils.precisionRound(temperature * 100, 0);
                await entity.write('hvacThermostat',
                    {0x4040: {value: convertedTemperature, type: herdsman.Zcl.DataType.int16}}, manufacturerOptions);
                return {state: {remote_temperature: temperature}};
            }
            
            if (key === 'valve_adapt_process') {
                if (value == true) {
                    const adaptStatus = utils.getFromLookup(meta.state.valve_adapt_status, adaptationStatus);

                    switch (adaptStatus) {
                    case adaptationStatus.ready_to_calibrate:
                    case adaptationStatus.error:
                        throw new Error ('Not implemented');
                        // await entity.command('hvacThermostat', 'boschCalibrateValve', {}, manufacturerOptions);
                        break;
                    default:
                        throw new Error('Valve adaption process not possible right now.');
                    }
                }

                return {state: {valve_adapt_process: value}};
            }
        },
        convertGet: async (entity, key, meta) => {
            switch (key) {
                case 'window_open':
                    await entity.read('hvacThermostat', [0x4042], manufacturerOptions);
                    break;
                case 'boost':
                    await entity.read('hvacThermostat', [0x4043], manufacturerOptions);
                    break;
                case 'system_mode':
                    await entity.read('hvacThermostat', [0x4007], manufacturerOptions);
                    break;
                case 'pi_heating_demand':
                    await entity.read('hvacThermostat', [0x4020], manufacturerOptions);
                    break;
                case 'remote_temperature':
                    await entity.read('hvacThermostat', [0x4040], manufacturerOptions);
                    break;
                case 'valve_adapt_process':
                    await entity.read('hvacThermostat', [0x4022], manufacturerOptions);
                    break;


                default: // Unknown key
                    throw new Error(`Unhandled key toZigbee.bosch_thermostat.convertGet ${key}`);
            }
        },
    },
    bosch_userInterface: {
        key: ['display_orientation', 'display_ontime', 'display_brightness', 'child_lock', 'displayed_temperature'],
        convertSet: async (entity, key, value, meta) => {
            if (key === 'display_orientation') {
                const index = displayOrientation[value];
                await entity.write('hvacUserInterfaceCfg', {0x400b: {value: index, type: herdsman.Zcl.DataType.uint8}}, manufacturerOptions);
                return {state: {display_orientation: value}};
            }
            if (key === 'display_ontime') {
                await entity.write('hvacUserInterfaceCfg', {0x403a: {value: value, type: herdsman.Zcl.DataType.enum8}}, manufacturerOptions);
                return {state: {display_onTime: value}};
            }
            if (key === 'display_brightness') {
                await entity.write('hvacUserInterfaceCfg', {0x403b: {value: value, type: herdsman.Zcl.DataType.enum8}}, manufacturerOptions);
                return {state: {display_brightness: value}};
            }
            if (key === 'child_lock') {
                const keypadLockout = Number(value === 'LOCK');
                await entity.write('hvacUserInterfaceCfg', {keypadLockout});
                return {state: {child_lock: value}};
            }
            if (key === 'displayed_temperature') {
                const index = displayedTemperature[value];
                await entity.write('hvacUserInterfaceCfg', {0x4039: {value: index, type: herdsman.Zcl.DataType.enum8}}, manufacturerOptions);
                return {state: {displayed_temperature: value}};
            }
        },
        convertGet: async (entity, key, meta) => {
            switch (key) {
                case 'display_orientation':
                    await entity.read('hvacUserInterfaceCfg', [0x400b], manufacturerOptions);
                    break;
                case 'display_ontime':
                    await entity.read('hvacUserInterfaceCfg', [0x403a], manufacturerOptions);
                    break;
                case 'display_brightness':
                    await entity.read('hvacUserInterfaceCfg', [0x403b], manufacturerOptions);
                    break;
                case 'child_lock':
                    await entity.read('hvacUserInterfaceCfg', ['keypadLockout']);
                    break;
                case 'displayed_temperature':
                    await entity.read('hvacUserInterfaceCfg', [0x4039], manufacturerOptions);
                    break;
                default: // Unknown key
                    throw new Error(`Unhandled key toZigbee.bosch_userInterface.convertGet ${key}`);
            }
        },
    },
};


const fzLocal = {
    bosch_ignore_dst: {
        cluster: 'genTime',
        type: 'read',
        convert: async (model, msg, publish, options, meta) => {
            if (msg.data.includes('dstStart', 'dstEnd', 'dstShift')) {
                const response = {
                    'dstStart': {attribute: 0x0003, status: herdsman.Zcl.Status.SUCCESS, value: 0x00},
                    'dstEnd': {attribute: 0x0004, status: herdsman.Zcl.Status.SUCCESS, value: 0x00},
                    'dstShift': {attribute: 0x0005, status: herdsman.Zcl.Status.SUCCESS, value: 0x00},
                };

                await msg.endpoint.readResponse(msg.cluster, msg.meta.zclTransactionSequenceNumber, response);
            }
        },
    },
    bosch_thermostat: {
        cluster: 'hvacThermostat',
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            const data = msg.data;
            if (data.hasOwnProperty(0x4040)) {
                result.remote_temperature = utils.precisionRound(data[0x4040] / 100, 2);
            }
            if (data.hasOwnProperty(0x4042)) {
                result.window_open = (Object.keys(stateOffOn)[data[0x4042]]);
            }
            if (data.hasOwnProperty(0x4043)) {
                result.boost = (Object.keys(stateOffOn)[data[0x4043]]);
            }
            if (data.hasOwnProperty(0x4020)) {
                result.pi_heating_demand = data[0x4020];
                result.running_state = result.pi_heating_demand >= 10 ? 'heat' : 'idle';
            }
            
            if (data.hasOwnProperty(0x4022)) {
                result.valve_adapt_status = utils.getFromLookupByValue(data[0x4022], adaptationStatus);

                switch (data[0x4022]) {
                case adaptationStatus.calibration_in_progress:
                    result.valve_adapt_process = true;
                    break;
                default:
                    result.valve_adapt_process = false;
                }
            }

            return result;
        },
    },
    bosch_userInterface: {
        cluster: 'hvacUserInterfaceCfg',
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            const data = msg.data;
            if (data.hasOwnProperty(0x400b)) {
                result.display_orientation = (Object.keys(displayOrientation)[data[0x400b]]);
            }
            if (data.hasOwnProperty(0x4039)) {
                result.displayed_temperature = (Object.keys(displayedTemperature)[data[0x4039]]);
            }
            if (data.hasOwnProperty(0x4007)) {
                result.system_mode = utils.getFromLookupByValue(data[0x4007], operatingModes);
            }
            if (data.hasOwnProperty(0x403a)) {
                result.display_ontime = data[0x403a];
            }
            if (data.hasOwnProperty(0x403b)) {
                result.display_brightness = data[0x403b];
            }
            if (data.hasOwnProperty('keypadLockout')) {
                result.child_lock = (data['keypadLockout'] == 1 ? 'LOCK' : 'UNLOCK');
            }

            return result;
        },
    },
};

const definition = [
    {
        zigbeeModel: ['RBSH-TRV0-ZB-EU'],
        model: 'BTH-RA',
        vendor: 'Bosch',
        description: 'Radiator thermostat II',
        ota: ota.zigbeeOTA,
        fromZigbee: [
            fz.thermostat,
            fz.battery,
            fzLocal.bosch_ignore_dst,
            fzLocal.bosch_thermostat,
            fzLocal.bosch_userInterface,
        ],
        toZigbee: [
            tz.thermostat_occupied_heating_setpoint,
            tz.thermostat_local_temperature_calibration,
            tz.thermostat_keypad_lockout,
            tzLocal.bosch_thermostat,
            tzLocal.bosch_userInterface,
        ],
        exposes: [
            e.climate()
                .withLocalTemperature(ea.STATE, 'Temperature used by the heating algorithm. ' +
                    'This is the temperature measured on the device (by default) or the remote temperature (if set within the last 30 minutes).')
                .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5)
                .withLocalTemperatureCalibration(-5, 5, 0.1)
                .withDescription('Only possible when local temp')
                .withSystemMode(['off', 'heat'])
                .withPiHeatingDemand(ea.ALL)
                .withRunningState(['idle', 'heat'], ea.STATE),
            e.binary('boost', ea.ALL, 'ON', 'OFF')
                .withDescription('Activate Boost heating'),
            e.binary('window_open', ea.ALL, 'ON', 'OFF')
                .withDescription('Window open'),
            e.enum('display_orientation', ea.ALL, Object.keys(displayOrientation))
                .withDescription('Display orientation'),
            e.numeric('remote_temperature', ea.ALL)
                .withValueMin(0)
                .withValueMax(35)
                .withValueStep(0.01)
                .withUnit('ยฐC')
                .withDescription('Input for remote temperature sensor. ' +
                    'This must be set at least every 30 minutes to prevent fallback to internal temperature reading!'),
            e.numeric('display_ontime', ea.ALL)
                .withValueMin(5)
                .withValueMax(30)
                .withDescription('Specifies the display On-time'),
            e.numeric('display_brightness', ea.ALL)
                .withValueMin(0)
                .withValueMax(10)
                .withDescription('Specifies the brightness value of the display'),
            e.enum('displayed_temperature', ea.ALL, Object.keys(displayedTemperature))
                .withDescription('Temperature displayed on the thermostat'),
            e.child_lock().setAccess('state', ea.ALL),
            e.battery(),
            e.enum('setpoint_change_source', ea.STATE, Object.keys(setpointSource))
                .withDescription('States where the current setpoint originated.'),
            e.enum('valve_adapt_status', ea.STATE, Object.keys(adaptationStatus))
                .withLabel('Adaptation status')
                .withDescription('Specifies the current status of the valve adaptation.'),
            e.binary('valve_adapt_process', ea.ALL, true, false)
                .withLabel('Trigger adaptation process')
                .withDescription('Trigger the valve adaptation process. Only possible when adaptation status ' +
                    'is "ready_to_calibrate" or "error".'),
        ],
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint = device.getEndpoint(1);
            
            const upgradeEndRequest = endpoint.waitForCommand('genOta', 'upgradeEndRequest', null, 2147483647);
            upgradeEndRequest.promise.then((data) => {
                logger.debug(`Got upgrade end request for '${device.ieeeAddr}': ${JSON.stringify(data.payload)}`);

                if (data.payload.status === 0) {
                    let fileSize = null;

                    switch (data.payload.fileVersion) {
                    // 3.04.03
                    case (872617236):
                        fileSize = 165916;
                        break;
                    // 3.05.05
                    case (889525524):
                        fileSize = 166009;
                        break;
                    // 3.05.09
                    case (889787668):
                        fileSize = 166554;
                        break;
                    }

                    const payload = {
                        manufacturerCode: data.payload.manufacturerCode, imageType: data.payload.imageType,
                        fileVersion: data.payload.fileVersion, imageSize: fileSize,
                        currentTime: 0, upgradeTime: 1,
                    };

                    endpoint.commandResponse('genOta', 'upgradeEndResponse', payload, null, data.header.transactionSequenceNumber).then(
                        () => {
                            logger.debug(`Update succeeded, waiting for device announce`);
                        },
                        (e) => {
                            const message = `Upgrade end response failed (${e.message})`;
                            logger.debug(message);
                        },
                    );
                } else {
                    // @ts-expect-error
                    const error = `Update failed with reason: '${data.payload.status}'`;
                    logger.debug(error);
                }
            });
            
            await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'hvacThermostat', 'hvacUserInterfaceCfg']);
            await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {min: 0, max: constants.repInterval.HOUR * 12, change: 1});
            await reporting.thermostatTemperature(endpoint, {min: 30, max: 900, change: 20});
            await reporting.thermostatKeypadLockMode(endpoint, {min: 0, max: constants.repInterval.HOUR * 12, change: null});
            await reporting.batteryPercentageRemaining(endpoint);

            // Report setpoint_change_source
            await endpoint.configureReporting('hvacThermostat', [{
                attribute: 'setpointChangeSource',
                minimumReportInterval: 0,
                maximumReportInterval: constants.repInterval.HOUR * 12,
                reportableChange: null,
            }]);
            // report operating_mode (system_mode)
            await endpoint.configureReporting('hvacThermostat', [{
                attribute: {ID: 0x4007, type: herdsman.Zcl.DataType.enum8},
                minimumReportInterval: 0,
                maximumReportInterval: constants.repInterval.HOUR * 12,
                reportableChange: null,
            }], manufacturerOptions);
            // report pi_heating_demand (valve opening)
            await endpoint.configureReporting('hvacThermostat', [{
                attribute: {ID: 0x4020, type: herdsman.Zcl.DataType.enum8},
                minimumReportInterval: 0,
                maximumReportInterval: constants.repInterval.HOUR * 12,
                reportableChange: null,
            }], manufacturerOptions);
            // Report valve_adapt_status (adaptation status)
            await endpoint.configureReporting('hvacThermostat', [{
                attribute: {ID: 0x4022, type: herdsman.Zcl.DataType.enum8},
                minimumReportInterval: 0,
                maximumReportInterval: constants.repInterval.HOUR * 12,
                reportableChange: null,
            }], manufacturerOptions);
            // report window_open
            await endpoint.configureReporting('hvacThermostat', [{
                attribute: {ID: 0x4042, type: herdsman.Zcl.DataType.enum8},
                minimumReportInterval: 0,
                maximumReportInterval: constants.repInterval.HOUR * 12,
                reportableChange: null,
            }], manufacturerOptions);
            // report boost as it's disabled by thermostat after 5 minutes
            await endpoint.configureReporting('hvacThermostat', [{
                attribute: {ID: 0x4043, type: herdsman.Zcl.DataType.enum8},
                minimumReportInterval: 0,
                maximumReportInterval: constants.repInterval.HOUR * 12,
                reportableChange: null,
            }], manufacturerOptions);

            await endpoint.read('hvacThermostat', ['localTemperatureCalibration', 'setpointChangeSource']);
            await endpoint.read('hvacThermostat', [0x4007, 0x4020, 0x4022, 0x4040, 0x4042, 0x4043], manufacturerOptions);

            await endpoint.read('hvacUserInterfaceCfg', ['keypadLockout']);
            await endpoint.read('hvacUserInterfaceCfg', [0x400b, 0x4039, 0x403a, 0x403b], manufacturerOptions);
        },
    },
];

module.exports = definition;

@DerDreschner, thank you a lot!

Sorry, what is WAF? I have central heating, so nothing bad has happened, I've just spent a day getting things working again, luckily everything was fine in the end.

So when I've used the converter for the first time, it did responded once to upgradeEndRequest, yet valves still failed to configure. And the valves kept sending this upgradeEndRequest again and again.

Check out the log (sorry for a lot of noise). External link to remove after.
bosch-trv-log20231226

Then I've changed the converter to keep responding every time, also changed the payload (removed imageSize and set updateTime to 0). That's the way ZHA integration does it according to logs I got.

here's only the changed part of configure method (await reporting.bind is the line for reference)

configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint = device.getEndpoint(1);

            function setWaitForUpgrade()
            {
                logger.debug(`Set to wait for 'upgradeEndRequest'  '${device.ieeeAddr}'`);
                const upgradeEndRequest = endpoint.waitForCommand('genOta', 'upgradeEndRequest', null, 2147483647);
                upgradeEndRequest.promise.then((data) => {
                    logger.debug(`Got upgrade end request for '${device.ieeeAddr}': ${JSON.stringify(data.payload)}, tsn '${data.header.transactionSequenceNumber}'`);
    
                    if (data.payload.status === 0) {
                        let fileSize = null;
    
                        switch (data.payload.fileVersion) {
                        // 3.04.03
                        case (872617236):
                            fileSize = 165916;
                            break;
                        // 3.05.05
                        case (889525524):
                            fileSize = 166009;
                            break;
                        // 3.05.09
                        case (889787668):
                            fileSize = 166554;
                            break;
                        }
    
                        const payload = {
                            manufacturerCode: data.payload.manufacturerCode, imageType: data.payload.imageType,
                            fileVersion: data.payload.fileVersion,
                            currentTime: 0, upgradeTime: 0,
                        };
    
                        endpoint.commandResponse('genOta', 'upgradeEndResponse', payload, null, data.header.transactionSequenceNumber).then(
                            () => {
                                logger.debug(`['${device.ieeeAddr}']Update succeeded, waiting for device announce, tsn '${data.header.transactionSequenceNumber}'`);
                                setWaitForUpgrade();
                            },
                            (e) => {
                                const message = `['${device.ieeeAddr}']Upgrade end response failed (${e.message}), tsn '${data.header.transactionSequenceNumber}'`;
                                logger.debug(message);
                                setWaitForUpgrade();
                            },
                        );
                    } else {
                        // @ts-expect-error
                        const error = `['${device.ieeeAddr}']Update failed with reason: '${data.payload.status}'`;
                        logger.debug(error);
                    }
                });
            };
            setWaitForUpgrade();
            
            await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'hvacThermostat', 'hvacUserInterfaceCfg']);
            

and it did the trick after some time. (responded 27 times to each valve ๐Ÿ˜ ) after that I could successfully configure and it would even return proper version and ask for update, which it didn't in this failed state.
here are the logs for the changed converter. log after changed converter

@bullmastiffo : WAF is the "wife acceptance factor", in this usage kind of a synonym for "I hope there is no bad mood in your household because of the broken smart home". ๐Ÿ˜„

Great to hear the issue is now resolved! I have a few comments/questions about your situation:

  • I guess the underlying problem is a firmware-related problem and may be fixed by the now-installed firmware 3.05.05 ("Stability improvements during the update process"). But I'm not sure how you got into that situation exactly (firmware update transmitted to the thermostats but no message to start the update process). I suspect your zigbee network is quite big? Do you remember if the firmware upgrade in Z2M was reported successful or if there were any error messages? Or did you used the thermostats on the native Bosch Smart Home Controller before without applying any updates?
  • It's interesting the first approach didn't work. I guess the fileSize you removed wasn't the problem here (can't find detailed documentation about being optional or not and what the device is supposed to use it for if transmitted - maybe @Koenkk can help out?), but the difference between currentTime and upgradeTime. As the thermostat wasn't able to be configured, it never requested the current time from Z2M thus may have never started their internal time. That means the upgradeTime was never hit as the currentTime was always 0. It's always the little details...

r the issue is now resolved! I have a few comments/questions about your situation:

ha ha, no worries, the smart house was back in place before anyone else could notice ๐Ÿ˜

  1. on getting into this state - unfortunately no data, I didn't follow it through, I've seen it uploading and then notification disappeared so I assumed it was ok, but seems like it got unnoticed unless I tried reconfiguring. I was using only z2m via HA skyconnect (always want to call it skynet)
  2. It could be the updateTime, or that bosch device somehow ignored some of the responses ( upgradeEndResponse) and reacted only eventually thus repetitive messages of upgradeEndRequest. I wonder if they could be lost or not. (I don't know if the messages use protocol with confirmation). I wonder if network around 25 devices is considered big or not.. From what I've seen people have much bigger number of devices

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 30 days