Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New device support]: Bosch Radiator Thermostat II #14926

Closed
niklasarnitz opened this issue Nov 11, 2022 · 85 comments
Closed

[New device support]: Bosch Radiator Thermostat II #14926

niklasarnitz opened this issue Nov 11, 2022 · 85 comments
Labels
new device support New device support request

Comments

@niklasarnitz
Copy link

Link

https://www.bosch-smarthome.com/at/de/produkte/geraete/heizkoerper-thermostat/

Database entry

{"id":22,"type":"EndDevice","ieeeAddr":"0x18fc26000002a2d7","nwkAddr":39513,"manufId":4617,"manufName":"BOSCH","powerSource":"Battery","modelId":"RBSH-TRV0-ZB-EU","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":769,"inClusterList":[0,1,3,4,32,513,516,2821],"outClusterList":[10,25],"clusters":{"genBasic":{"attributes":{"modelId":"RBSH-TRV0-ZB-EU","manufacturerName":"BOSCH","powerSource":3,"zclVersion":8,"appVersion":1,"stackVersion":5,"hwVersion":1,"dateCode":"20220627","swBuildId":"3.02.05"}},"genPollCtrl":{"attributes":{"checkinInterval":6480}},"hvacThermostat":{"attributes":{"20496":0}}},"binds":[{"cluster":32,"type":"endpoint","deviceIeeeAddress":"0x00124b0024c20f21","endpointID":1}],"configuredReportings":[],"meta":{}}},"appVersion":1,"stackVersion":5,"hwVersion":1,"dateCode":"20220627","swBuildId":"3.02.05","zclVersion":8,"interviewCompleted":true,"meta":{},"lastSeen":1668172257367,"defaultSendRequestWhen":"active"}

Comments

Here are some things I found out and got working already:

  • It pairs successfully after adding the Install Code
  • I cannot get it to read the temperature or write the temperature. Maybe I'm just not understanding the concepts of z2mqtt that well.
    Here is my first draft of a config:
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 e = exposes.presets;
const ea = exposes.access;

const definition = {
    zigbeeModel: ['RBSH-TRV0-ZB-EU'],
    model: 'Radiator thermostat II',
    vendor: 'Bosch',
    description: 'Radiator thermostat',
    fromZigbee: [],
    toZigbee: [fz.thermostat, fz.hvac_user_interface],
    exposes: [
	   e.child_lock(),
	   exposes.climate()
	   	.withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET)
	   	.withLocalTemperature()
    ],
};

module.exports = definition;

Comment: The setpoint range and interval should be correct.

External converter

No response

Supported color modes

No response

Color temperature range

No response

@niklasarnitz niklasarnitz added the new device support New device support request label Nov 11, 2022
@itkama
Copy link

itkama commented Nov 11, 2022

This seems to be a duplicate of #14005 - though more people with this device should hopefully speed things up :)

@danieledwardgeorgehitchcock
Copy link
Contributor

You might want to add a fromZigbee converter to read back the temperature from the device.

If you have the Z2M GUI enabled, you can see what clusters are supported by the endpoints, in the device clusters tab.

You can also see what the device reports back using the device dev tab and reading from the various clusters.

@danieledwardgeorgehitchcock
Copy link
Contributor

danieledwardgeorgehitchcock commented Nov 11, 2022

Try adding fz.thermostat to your fromZigbee section - also remove what you have from your toZigbee section (for now)

@itkama
Copy link

itkama commented Nov 11, 2022

@danieledwardgeorgehitchcock
I've played around a bit with the dev tab you mentioned.

Via Endpoint 1, Cluster hvacThermostat I can read the following attributes:
maxHeatSetpointLimit
minHeatSetpointLimit
localTemp
occupiedHeatingSetpoint

I can also set the last one if I write instead of reading it.
For the last one I get this answer:
Read result of 'hvacThermostat': {"occupiedHeatingSetpoint":2000}
It's the current heating setpoint which is set at 20°C.

How would I now add these commands to converter file @niklasarnitz started above?

@danieledwardgeorgehitchcock
Copy link
Contributor

danieledwardgeorgehitchcock commented Nov 11, 2022

So, I personally would look at getting the read attributes in and working first - This is what fromZigbee converters do. I'd then work on the write attributes which are things like your occupiedHeatingSetpoint, etc which is what toZigbee converters do.

Here is something that should get you some of the way to a functional product:

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 e = exposes.presets;
const ea = exposes.access;

const definition = {
    zigbeeModel: ['RBSH-TRV0-ZB-EU'],
    model: 'Radiator thermostat II',
    vendor: 'Bosch',
    description: 'Radiator thermostat',
    fromZigbee: [fz.thermostat, fz.hvac_user_interface],
    toZigbee: [tz.thermostat_occupied_heating_setpoint],
    exposes: [
	   exposes.climate()
	   	.withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.STATE_SET)
	   	.withLocalTemperature()
    ],
};

module.exports = definition;

@niklasarnitz
Copy link
Author

I'll be trying this tomorrow.
But the last thread entry by @danieledwardgeorgehitchcock should propably work.

@itkama
Copy link

itkama commented Nov 11, 2022

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 e = exposes.presets;
const ea = exposes.access;

const definition = {
    zigbeeModel: ['RBSH-TRV0-ZB-EU'],
    model: 'Radiator Thermostat II',
    vendor: 'Bosch',
    description: 'Radiator Thermostat',
    fromZigbee: [fz.thermostat, fz.hvac_user_interface, fz.battery],
    toZigbee: [tz.thermostat_occupied_heating_setpoint],
    exposes: [
        e.battery(),
	    exposes.climate()
	    .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.STATE_SET)
	    .withLocalTemperature()
    ],
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat', 'genPowerCfg']);
        await reporting.thermostat_occupied_heating_setpoint(endpoint);
        await reporting.thermostatTemperature(endpoint);
        await reporting.batteryVoltage(endpoint);
        await reporting.battery(endpoint);
    },
};

module.exports = definition;

This is my current result. Changing the setpoint works, temperature gets reported and also the battery percentage:
image

What would definetly be nice to have: A current running state of the device to have it display that (in my case) in Home Assistant.

@niklasarnitz
Copy link
Author

When using @itkama 's config, I get a few different errors:

When I try and refresh the temperature:

Debug 2022-11-12 03:19:28 Received MQTT message on 'zigbee2mqtt/0x18fc26000002a2d7/get' with data '{"local_temperature":""}'
Error 2022-11-12 03:19:28 No converter available for 'local_temperature' ("")

And it seems that the binding process of genPowerCfg fails:

x18fc26000005976f/1 genPowerCfg from '0x00124b0024c20f21/1' failed (AREQ - ZDO - bindRsp after 10000ms)
zigbee2mqtt_1        |     at Timeout._onTimeout (/app/node_modules/zigbee-herdsman/src/utils/waitress.ts:64:35)
zigbee2mqtt_1        |     at listOnTimeout (node:internal/timers:559:17)
zigbee2mqtt_1        |     at processTimers (node:internal/timers:502:7))
zigbee2mqtt_1        | Zigbee2MQTT:info  2022-11-12 03:12:37: MQTT publish: topic 'zigbee2mqtt/bridge/response/device/configure', payload '{"data":{"id":"0x18fc26000005976f"},"error":"Failed to configure (Bind 0x18fc26000005976f/1 genPowerCfg from '0x00124b0024c20f21/1' failed (AREQ - ZDO - bindRsp after 10000ms))","status":"error","transaction":"uxdrr-1"}'

Setting the temperature works.

Here's a screenshot of my GUI:
Screenshot from 2022-11-12 03-21-33

@itkama
Copy link

itkama commented Nov 12, 2022

Interestingly I get the same error when trying to refresh the temperature manually. But the thermostat seems to be doing that itself quite frequently in the background. But those messages from the thermostat only started appearing once I poked it via the dev console.

Does it work for you if you ask for it manually?
image

Also added the temperature calibration:

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 e = exposes.presets;
const ea = exposes.access;

const definition = {
    zigbeeModel: ["RBSH-TRV0-ZB-EU"],
    model: "Radiator Thermostat II",
    vendor: "Bosch",
    description: "Radiator Thermostat",
    fromZigbee: [fz.thermostat, fz.battery],
    toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, tz.thermostat_local_temperature],
    exposes: [
        e.battery(),
	exposes.climate()
	.withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.STATE_SET)
	.withLocalTemperature()
        .withLocalTemperatureCalibration(-30, 30, 0.1)  //Values are a guess and stolen from a eurotronic thermostat valve
    ],
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'hvacThermostat']);
        await reporting.thermostat_occupied_heating_setpoint(endpoint);
        await reporting.thermostatTemperature(endpoint);
        await reporting.thermostatTemperatureCalibration(endpoint);
        await reporting.battery(endpoint);
    },
};

module.exports = definition;

It seems to have the same problem that the refresh button doesn't work. Using the slider does though.

@niklasarnitz
Copy link
Author

Refresh now works for me. Also the Local Temp works.

But here is another error:

zigbee2mqtt_1        | Zigbee2MQTT:error 2022-11-12 17:00:27: Failed to configure 'EG_Zimmer_Straße_Thermostat_Rechts', attempt 2 (ReferenceError: reporting is not defined
zigbee2mqtt_1        |     at Object.configure (/app/dist/util/externally-loaded.js:23:9)
zigbee2mqtt_1        |     at Configure.configure (/app/lib/extension/configure.ts:115:37)
zigbee2mqtt_1        |     at EventEmitter.<anonymous> (/app/lib/extension/configure.ts:80:62)
zigbee2mqtt_1        |     at EventEmitter.emit (node:events:525:35)
zigbee2mqtt_1        |     at EventBus.emitLastSeenChanged (/app/lib/eventBus.ts:52:22)
zigbee2mqtt_1        |     at Controller.<anonymous> (/app/lib/zigbee.ts:66:27)
zigbee2mqtt_1        |     at Controller.emit (node:events:513:28)
zigbee2mqtt_1        |     at Controller.selfAndDeviceEmit (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:515:14)
zigbee2mqtt_1        |     at Controller.onZclOrRawData (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:727:18)
zigbee2mqtt_1        |     at ZStackAdapter.<anonymous> (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:144:70))

@danieledwardgeorgehitchcock
Copy link
Contributor

Refresh now works for me. Also the Local Temp works.

But here is another error:

zigbee2mqtt_1        | Zigbee2MQTT:error 2022-11-12 17:00:27: Failed to configure 'EG_Zimmer_Straße_Thermostat_Rechts', attempt 2 (ReferenceError: reporting is not defined
zigbee2mqtt_1        |     at Object.configure (/app/dist/util/externally-loaded.js:23:9)
zigbee2mqtt_1        |     at Configure.configure (/app/lib/extension/configure.ts:115:37)
zigbee2mqtt_1        |     at EventEmitter.<anonymous> (/app/lib/extension/configure.ts:80:62)
zigbee2mqtt_1        |     at EventEmitter.emit (node:events:525:35)
zigbee2mqtt_1        |     at EventBus.emitLastSeenChanged (/app/lib/eventBus.ts:52:22)
zigbee2mqtt_1        |     at Controller.<anonymous> (/app/lib/zigbee.ts:66:27)
zigbee2mqtt_1        |     at Controller.emit (node:events:513:28)
zigbee2mqtt_1        |     at Controller.selfAndDeviceEmit (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:515:14)
zigbee2mqtt_1        |     at Controller.onZclOrRawData (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:727:18)
zigbee2mqtt_1        |     at ZStackAdapter.<anonymous> (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:144:70))

Add const reporting = require('zigbee-herdsman-converters/lib/reporting'); into your declarations at the top of your file

@itkama
Copy link

itkama commented Nov 12, 2022

@danieledwardgeorgehitchcock I'm getting this error now:
Failed to configure 'Manuel Thermostat', attempt 3 (TypeError: reporting.thermostat_occupied_heating_setpoint is not a function at Object.configure (/app/dist/util/externally-loaded.js:25:25) at Configure.configure (/app/lib/extension/configure.ts:115:13))

@danieledwardgeorgehitchcock
Copy link
Contributor

replace await reporting.thermostat_occupied_heating_setpoint(endpoint); with await reporting.thermostatOccupiedHeatingSetpoint(endpoint);

@itkama
Copy link

itkama commented Nov 12, 2022

New error now popping up (it's still working though):

Failed to configure 'Manuel Thermostat', attempt 1 
(Error: ConfigureReporting 0x18fc26000002fc33/1 hvacThermostat([{"attribute":"localTemperatureCalibration","minimumReportInterval":0,"maximumReportInterval":3600,"reportableChange":0}], 
{"sendWhen":"active","timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"srcEndpoint":null,"reservedBits":0,"manufacturerCode":null,"transactionSequenceNumber":null,"writeUndiv":false}) 
failed (Status 'UNREPORTABLE_ATTRIBUTE') 
at Endpoint.checkStatus (/app/node_modules/zigbee-herdsman/src/controller/model/endpoint.ts:317:28) 
at Endpoint.configureReporting (/app/node_modules/zigbee-herdsman/src/controller/model/endpoint.ts:694:22) 
at Object.thermostatTemperatureCalibration (/app/node_modules/zigbee-herdsman-converters/lib/reporting.js:151:9) 
at Object.configure (/app/dist/util/externally-loaded.js:27:9) 
at Configure.configure (/app/lib/extension/configure.ts:115:13))

@danieledwardgeorgehitchcock
Copy link
Contributor

It's telling you what the issue is. localTemperatureCalibration is not reportable from the device

@itkama
Copy link

itkama commented Nov 12, 2022

The question would be why it is telling me this? Via the console of this device it is possible:
image

@danieledwardgeorgehitchcock
Copy link
Contributor

You're reading an attribute there, not asking it to report it given a threshold or interval. Try doing the same thing manually in the reporting tab and you'll likely get the same error...

@mmattel
Copy link

mmattel commented Nov 20, 2022

Is there any progress so that the new device info can be added and published in a near future?

@niklasarnitz
Copy link
Author

I have a working device config now.

I’ll create a PR shortly.

@Koenkk
Copy link
Owner

Koenkk commented Nov 20, 2022

Added by @niklasarnitz (in Koenkk/zigbee-herdsman-converters#4983)

Changes will be available in the dev branch in a few hours from now. (https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html)

@Koenkk Koenkk closed this as completed Nov 20, 2022
@mmattel
Copy link

mmattel commented Nov 21, 2022

@Koenkk do you have an idea when a new stable release will be published?

@danieledwardgeorgehitchcock
Copy link
Contributor

Stable releases are at the beginning of each month

@BBJake
Copy link

BBJake commented Nov 25, 2022

image

I added some more functions for the device, fixed some values and mapped the system_mode to the Bosch configuration_mode

@BBJake
Copy link

BBJake commented Nov 25, 2022

const herdsman = require('zigbee-herdsman');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
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 utils = require("zigbee-herdsman-converters//lib/utils");
const assert = require('assert');
const e = exposes.presets;
const ea = exposes.access;

const boschManufacturer = {manufacturerCode: 0x1209};

const operatingModes = {
    'automatic': 0,
    'manual': 1,
    'pause': 5,
};

const stateOffOn = {
    'OFF': 0,
    'ON': 1,
};

const displayOrientation = {
    'normal': 0,
    'flipped': 1,
};


const tzLocal = {
    bosch_thermostat: {
        key: ['window_open','boost','system_mode'],
        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}}, boschManufacturer);
                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}}, boschManufacturer);
                return {state: {boost: value}};
            }
            if (key === 'system_mode') {
		// Map system_mode (Off/Auto/Heat) to Boschg operating mode
                value = value.toLowerCase();

		let opMode = 1; // OperatingMode 1 = Manual (Default)
		if (value=='off') { opMode = 5 } // OperatingMode 5 = Pause 
		else if (value=='auto') { opMode = 0 } // OperatingMOde 1 = Automatic 

                await entity.write('hvacThermostat', {0x4007: {value: opMode, type: herdsman.Zcl.DataType.enum8}}, boschManufacturer);
                return {state: {system_mode: value}};
            }

        },
        convertGet: async (entity, key, meta) => {
            switch (key) {
            case 'window_open':
                await entity.read('hvacThermostat', [0x4042], boschManufacturer);
                break;
            case 'boost':
                await entity.read('hvacThermostat', [0x4043], boschManufacturer);
                break;
            case 'system_mode':
                await entity.read('hvacThermostat', [0x4007], boschManufacturer);
                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'],
        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}}, boschManufacturer);
                return {state: {display_orientation: value}};
            }
           if (key === 'display_ontime') {
                await entity.write('hvacUserInterfaceCfg', {0x403a: {value: value, type: herdsman.Zcl.DataType.enum8}}, boschManufacturer);
                return {state: {display_onTime: value}};
            }
           if (key === 'display_brightness') {
                await entity.write('hvacUserInterfaceCfg', {0x403b: {value: value, type: herdsman.Zcl.DataType.enum8}}, boschManufacturer);
                return {state: {display_brightness: value}};
            }
            if (key === 'child_lock') {
            	const keypadLockout = Number(value==='LOCK');
            	await entity.write('hvacUserInterfaceCfg', {keypadLockout});
            	return {state: {child_lock: value}};
	    }
        },
        convertGet: async (entity, key, meta) => {
            switch (key) {
            case 'display_orientation':
                await entity.read('hvacUserInterfaceCfg', [0x400b], boschManufacturer);
                break;
            case 'display_ontime':
                await entity.read('hvacUserInterfaceCfg', [0x403a], boschManufacturer);
                break;
            case 'display_brightness':
                await entity.read('hvacUserInterfaceCfg', [0x403b], boschManufacturer);
                break;
            case 'child_lock':
		await entity.read('hvacUserInterfaceCfg', ['keypadLockout']);
                break;
            default: // Unknown key
                throw new Error(`Unhandled key toZigbee.bosch_userInterface.convertGet ${key}`);
            }
        },
    },
};


const fzLocal = {
    bosch_thermostat: {
        cluster: 'hvacThermostat',
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            const data = msg.data;
            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(0x4007)) {
		const opModes = {0: 'auto', 1: 'heat', 2: 'unknowm 2', 3: 'unknonw 3', 4: 'unknown 4', 5: 'off'};
                result.system_mode = opModes[data[0x4007]];
            }

            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(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: "Radiator Thermostat II",
    vendor: "Bosch",
    description: "Radiator Thermostat",
    fromZigbee: [fz.thermostat, fz.battery, fzLocal.bosch_thermostat, fzLocal.bosch_userInterface],
    toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, tz.thermostat_local_temperature, tz.thermostat_keypad_lockout, tzLocal.bosch_thermostat, tzLocal.bosch_userInterface],
    exposes: [
	exposes.climate()
	  .withLocalTemperature()
	  .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.STATE_SET)
          .withLocalTemperatureCalibration(-12, 12, 0.5)
	  .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET),
        exposes.binary('boost', ea.ALL, 'ON', 'OFF')
                .withDescription('Activate Boost heating'),
        exposes.binary('window_open', ea.ALL, 'ON', 'OFF')
                .withDescription('Window open'),
	e.child_lock(),
	exposes.enum('display_orientation', ea.ALL, Object.keys(displayOrientation))
		.withDescription('Display orientation'),
	exposes.numeric('display_ontime', ea.ALL).withValueMin(5).withValueMax(30)
                .withDescription('Specifies the diplay On-time'),
	exposes.numeric('display_brightness', ea.ALL).withValueMin(0).withValueMax(10)
                .withDescription('Specifies the brightness value of the display'),
        e.battery(),
    ],
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'hvacThermostat','hvacUserInterfaceCfg']);
	await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
        await reporting.thermostatTemperature(endpoint);
        await reporting.batteryPercentageRemaining(endpoint);

	await endpoint.read('hvacThermostat',['localTemperatureCalibration']);
        await endpoint.read('hvacThermostat', [0x4007, 0x4042, 0x4043], boschManufacturer);

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

module.exports = definition;


@mmattel
Copy link

mmattel commented Nov 25, 2022

@BBJake I have updated the integration according your code, but because my Conbee II currently does not support "Installation Codes", could you test the upgrade?

@Nezz
Copy link

Nezz commented Jan 3, 2023

I had another case where the thermostat went crazy overnight, switching between "occupiedHeatingSetpoint":500 and "occupiedHeatingSetpoint":1700. I have another thermostat that I use as a reference with the Bosch Smart Home Controller and this has not happened there so far.

It started with failing to set the heating to 17:

debug 2023-01-03 00:00:00: Publishing 'set' 'occupied_heating_setpoint' to 'Bedroom Thermostat'
debug 2023-01-03 00:26:36: Received Zigbee message from 'Bedroom Thermostat', type 'attributeReport', cluster 'hvacThermostat', data '{"localTemp":2320}' from endpoint 1 with groupID 0
error 2023-01-03 00:27:06: Publish 'set' 'occupied_heating_setpoint' to 'Bedroom Thermostat' failed: 'Error: Write 0x18fc26000006aefd/1 hvacThermostat({"occupiedHeatingSetpoint":1700}, {"sendWhen":"active","timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"srcEndpoint":null,"reservedBits":0,"manufacturerCode":null,"transactionSequenceNumber":null,"writeUndiv":false}) failed (Data request failed with error: 'MAC no ack' (233))'

Then a couple hours later setting the temperature twice:

debug 2023-01-03 02:20:17: Publishing 'set' 'occupied_heating_setpoint' to 'Bedroom Thermostat'
debug 2023-01-03 02:21:04: Received MQTT message on 'zigbee2mqtt/Bedroom Thermostat/set' with data '{"occupied_heating_setpoint":17}'
debug 2023-01-03 02:21:04: Publishing 'set' 'occupied_heating_setpoint' to 'Bedroom Thermostat'

Logs with MQTT publish removed: https://pastebin.com/NDPsA3ny

The log contains several thermostat restarts that did not help. Restarting Z2M then the thermostat helped.

@compujuckel
Copy link

I've extracted the OTA file from a Zigbee packet capture and successfully used it to update one of my thermostats via z2m:
Koenkk/zigbee-OTA#231
Koenkk/zigbee-herdsman-converters#5283

@niklasarnitz
Copy link
Author

niklasarnitz commented Jan 6, 2023 via email

@compujuckel
Copy link

I haven't done any captures from SHC <-> Internet since everything is probably encrypted anyways.
Sniffing the Zigbee traffic is easy, it just requires a bit of patience :D

Also since I only borrowed the SHC from a relative I don't want to play around with it too much...

@niklasarnitz
Copy link
Author

Could you capture the traffic?

URLs aren’t encoded so getting the endpoints would be great too.

And maybe if there is a button for ‘Check for controller firmware upgrade’ that capture would be great too.

With those two things, I could start fuzzing the Bosch API

@compujuckel
Copy link

It was already confirmed in this thread that the SHC uses HTTPS to communicate with the Bosch backend. You can't get URLs from that.

@niklasarnitz
Copy link
Author

Ah. Sorry. My bad.

Could you have a look ad DNS queries made by the controller?

@niklasarnitz
Copy link
Author

I also just bought a smart home controller, so maybe I can dissect the firmware soon :)

@compujuckel
Copy link

compujuckel commented Jan 6, 2023

shc_dns.zip
Attached a pcap filtered by DNS. It includes startup and pairing a thermostat to the controller.

@compujuckel
Copy link

Initial support for using a remote temperature sensor: Koenkk/zigbee-herdsman-converters#5297
This is probably a much better way than using better_thermostat with local temperature calibration, but I still haven't figured out yet how to change back to the internal temperature sensor (pulling the batteries works though).

Getting this to work properly would most likely require buying a Raumthermostat II and taking a look at the traffic...

@niklasarnitz
Copy link
Author

Okay, I have good and bad news on the SHC Hacking front:

The good news: I have received my SHC 1

The bad news: Multiple Attack surfaces I have tried until now have failed.
They include:

  • Probing the board for an UART -> None found.
  • Trying to intercept the API Calls to the Bosch servers via mitmproxy -> Failed, the SHC doesn't care about proxies and there is no way to install a CA on it without root access

I'm currently thinking about more possibilities, f.e. desoldering the EMMC flash and dumping the firmware that way, but currently I do not have access to BGA soldering equipment.

Does anybody else have an Idea I could try?

@itkama
Copy link

itkama commented Jan 10, 2023

How exactly are you trying to use a mitmproxy? As long as Bosch doesn't validate the certificate the proxy should work? Or am I just understanding you wrong, that Bosch is exactly doing that? (Which would be good, in a sense that Bosch is doing something right, bad for trying to hack it :D)

@niklasarnitz
Copy link
Author

niklasarnitz commented Jan 10, 2023

I tried using it as a transparent proxy and I also tried using a simple DNS spoof.
Both things didn't work. They must check for valid certificates.

Also, the only open port on their API server that handles the updates is 443 ;)

@itkama
Copy link

itkama commented Jan 10, 2023

On a more positive note: Bosch has read the feedback about the need for the files. It's basically nothing, but atleast someone inside of Bosch has atleast heard of our wishes now:
image
(Before this they were adamant that updates can only be made via the SHC and that they cannot provide any files ever. After asking why, the answer above happened.)

@BBJake
Copy link

BBJake commented Jan 15, 2023

It also got one Bosch SHC for 30 EUR ...
Mainly I wanted to update my devices to the latest firmware and see if I can find out anything about where the SHC pulls the OTA files from.
As everything is SSL encrypted, you can't see any URLs, but with my Pi-Hole i just saw connections to these domains:

  • rollouts-cs.bosch-smarthome.com
  • sh-geo-ip-p1.apps.bosch-iot-cloud.com
  • sh-rcum-p1.s-apps.de1.bosch-iot-cloud.com

The first one sounds like a good candidate, but bosch seems to use client certificates to communicate with their servers:
image

@niklasarnitz
Copy link
Author

Do you have the Smarthome Controller 1 or 2?

Model Variant 2 uses another CPU and maybe there is a serial shell available on that ones PCB.

@BBJake
Copy link

BBJake commented Jan 15, 2023

I also have the SHC 1

@itkama
Copy link

itkama commented Jan 15, 2023

@niklasarnitz I would assume he has the SHC1 - the SHC2 seems not to be available for purchase yet. Says "In Kürze" on Boschs website and I also can't find it anywhere else to buy. Got a steep price point of 100€ too...

@niklasarnitz
Copy link
Author

niklasarnitz commented Jan 15, 2023

Hm. thats unfortunate.

@BBJake could you probe your board for a possible UART?
Maybe you will find something I havent.

@BBJake
Copy link

BBJake commented Jan 15, 2023

@niklasarnitz
Sorry, but I don't have the necessary hardware for that...

@itkama
Copy link

itkama commented Feb 1, 2023

@compujuckel According to Bosch there should soon be another update for the thermostat (end of january/start of february).
Would be really nice, if you could again extract it from a packet capture once it is available.

@niklasarnitz
Copy link
Author

If someone could tell me how to perform the packet capture, I can do that too.

@compujuckel
Copy link

@niklasarnitz that would be great, I currently don't have access to an SHC anymore.
Do you have a CC2531 or similar Zigbee sniffer lying around? This guide explains most of it: https://www.zigbee2mqtt.io/advanced/zigbee/04_sniff_zigbee_traffic.html

I can post my program to reassemble the OTA file from a packet capture if you or anyone else needs it.
Also you might need more than one capture due to packet loss

@TheJulianJES
Copy link

@compujuckel Did you post your script anywhere? Testing something similar with one Aqara device where the hub seems to modify the image a bit.

@compujuckel
Copy link

It's released here now: https://github.com/compujuckel/ZigbeeOtaExtractor
let me know if you have any questions

@mmattel
Copy link

mmattel commented Feb 24, 2023

Maybe of interest: zigpy/zigpy-cli#29 (Reconstruct OTA images from PCAP files)
Also see Koenkk/zigbee-OTA#263

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new device support New device support request
Projects
None yet
Development

No branches or pull requests

10 participants