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 Room Thermostat II #16596

Closed
Thxlx opened this issue Feb 9, 2023 · 20 comments
Closed

[New device support]: Bosch Room Thermostat II #16596

Thxlx opened this issue Feb 9, 2023 · 20 comments
Labels
new device support New device support request stale Stale issues

Comments

@Thxlx
Copy link

Thxlx commented Feb 9, 2023

Link

https://www.bosch-smarthome.com/de/de/produkte/geraete/raumthermostat/

Database entry

{"id":7,"type":"EndDevice","ieeeAddr":"0x001e5e090290112a","nwkAddr":15817,"manufId":4617,"manufName":"Bosch","powerSource":"Battery","modelId":"RBSH-RTH0-BAT-ZB-EU","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":769,"inClusterList":[0,1,3,32,513,516,1029,2821],"outClusterList":[10,25],"clusters":{"genBasic":{"attributes":{"modelId":"RBSH-RTH0-BAT-ZB-EU","manufacturerName":"Bosch","powerSource":3,"zclVersion":8,"appVersion":1,"stackVersion":2,"hwVersion":0,"dateCode":"221020"}},"genPollCtrl":{"attributes":{"checkinInterval":6480}},"hvacThermostat":{"attributes":{"16391":1,"16450":0,"16451":0,"localTemp":2400,"occupiedHeatingSetpoint":1850,"setpointChangeSource":1,"runningState":0,"systemMode":4}},"msRelativeHumidity":{"attributes":{"measuredValue":2781}},"genPowerCfg":{"attributes":{"batteryVoltage":61}},"hvacUserInterfaceCfg":{"attributes":{"16442":30}}},"binds":[{"cluster":32,"type":"endpoint","deviceIeeeAddress":"0x00124b0026b89307","endpointID":242},{"cluster":513,"type":"endpoint","deviceIeeeAddress":"0x00124b0026b89307","endpointID":1},{"cluster":1026,"type":"endpoint","deviceIeeeAddress":"0x00124b0026b89307","endpointID":1},{"cluster":1029,"type":"endpoint","deviceIeeeAddress":"0x00124b0026b89307","endpointID":1},{"cluster":0,"type":"endpoint","deviceIeeeAddress":"0x00124b0026b89307","endpointID":1},{"cluster":1,"type":"endpoint","deviceIeeeAddress":"0x00124b0026b89307","endpointID":1},{"cluster":516,"type":"endpoint","deviceIeeeAddress":"0x00124b0026b89307","endpointID":1}],"configuredReportings":[{"cluster":513,"attrId":18,"minRepIntval":0,"maxRepIntval":3600,"repChange":10},{"cluster":513,"attrId":0,"minRepIntval":0,"maxRepIntval":3600,"repChange":10}],"meta":{}}},"appVersion":1,"stackVersion":2,"hwVersion":0,"dateCode":"221020","zclVersion":8,"interviewCompleted":true,"meta":{},"lastSeen":1675940022189,"defaultSendRequestWhen":"active"}

Comments

Hey!
I am just getting started with z2m and tried to implement my Bosch Room Thermostat II. I got most of it working by copy pasting this comment related to the Radiator Thermostat, deleting the unsupported stuff and adding the humidity sensor.

This is what I tinkered together:

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 tzLocal = {
    bosch_thermostat: {
        key: ['window_open','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 === '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 '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_ontime','display_brightness','child_lock'],
        convertSet: async (entity, key, value, meta) => {
        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_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(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(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-RTH0-BAT-ZB-EU"],
    model: "Room Thermostat II",
    vendor: "Bosch",
    description: "Room Thermostat",
    fromZigbee: [fz.humidity, 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: [
        e.humidity(),
	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('window_open', ea.ALL, 'ON', 'OFF')
                .withDescription('Window open'),
	e.child_lock(),
	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;

External converter

No response

Supported color modes

No response

Color temperature range

No response

@Thxlx Thxlx added the new device support New device support request label Feb 9, 2023
@rvweert
Copy link

rvweert commented Feb 14, 2023

Hey @Thxlx were you able to pair it? I am trying this too however interviewing fails and I don't get to add it. Is there something specific you did in order to pair it?
Cheers,
Rene

@Thxlx
Copy link
Author

Thxlx commented Feb 14, 2023

You'll need to add the Install Code found inside the battery compartment to z2m. (Settings -> Tools -> Install code)
After that I got the interview process to succeed :)

@Starcon79
Copy link

Starcon79 commented Feb 14, 2023

Hello, first of all thanks for adding the thermostat. I also managed to install the device. Unfortunately, nothing is displayed for battery and humidity. Is that the case with you too? Or do I have to set something else?

screenshot-192 168 178 90_8123-2023 02 14-11_48_44

greeting
Torsten

@Thxlx
Copy link
Author

Thxlx commented Feb 14, 2023

Hi Torsten!
Humidity worked fine for me but unfortunately I didn't get the battery level working.
I am sorry, but I do not have the needed experience to fix this as this is more of a proof of concept / workaround until theres somebody experienced enough to do it properly 😅

@rvweert
Copy link

rvweert commented Feb 14, 2023

Thanks @Thxlx - It was indeed the install code. I added it once before but didn't know you need to add it every time you wish to pair a device. 👍

@henkiejan1
Copy link

Is there any opportunity that this wall thermostat would be supported by Zigbee2mqtt self? It looks like a nice replacement for the Max! EQ-3 system. What i see it wil work mostly like the same but uses the Zigbee protocol? So you can put a TRV on the main valve in my apartment and hang the wall thermostat in the living room to control the temperature there? That''s the situation right now at my home. :)

@rvweert
Copy link

rvweert commented Feb 16, 2023

Actually when I look at the logs I don't see any humidity data. Is any of you seeing this? Can you share some data then perhaps? Thanks in advance.

@Starcon79
Copy link

Starcon79 commented Feb 17, 2023

@Thxlx I think I got the battery status working. The only thing missing for me now is the humidity. I think then we have everything.
Hope this helps us.
1676647108835

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 tzLocal = {
bosch_thermostat: {
key: ['window_open','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 === '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 '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_ontime','display_brightness','child_lock'],
    convertSet: async (entity, key, value, meta) => {
    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_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(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(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-RTH0-BAT-ZB-EU"],
model: "Room Thermostat II",
vendor: "Bosch",
description: "Room Thermostat",
fromZigbee: [fz.humidity, 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.numeric('humidity', ea.STATE).withUnit('%'),
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('window_open', ea.ALL, 'ON', 'OFF')
.withDescription('Window open'),
e.child_lock(),
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_low(),
],
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;`

@github-actions
Copy link
Contributor

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

@github-actions github-actions bot added the stale Stale issues label Mar 20, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Mar 28, 2023
@FabiJa
Copy link

FabiJa commented May 27, 2023

Has anybody managed to get this thermostat working including humidity?

@dennismaxjung
Copy link

I've currently testing a slightly changed converter.

https://gist.github.com/dennismaxjung/22a72278bf97f1a2581a7270e766cff3

image

Currently everything seems to work

@DeeBee84
Copy link

DeeBee84 commented Aug 12, 2023

Hello dennismaxjung, I tried your converter. But I'm still not getting any humidity readings.
The log file shows an error. Is this a local Problem?

Log:
Error 2023-08-12 12:40:22Failed to configure '0x001e5e09029016ea', attempt 1 (Error: Read 0x001e5e09029016ea/1 hvacUserInterfaceCfg([16395,16442,16443], {"sendWhen":"active","timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"srcEndpoint":null,"reservedBits":0,"manufacturerCode":4617,"transactionSequenceNumber":null,"writeUndiv":false}) failed (Status 'UNSUPPORTED_ATTRIBUTE') at Endpoint.checkStatus (/opt/zigbee2mqtt/node_modules/zigbee-herdsman/src/controller/model/endpoint.ts:419:28) at Endpoint.read (/opt/zigbee2mqtt/node_modules/zigbee-herdsman/src/controller/model/endpoint.ts:561:22) at Object.configure (/opt/zigbee2mqtt/data/extension/externally-loaded.js:165:9) at Configure.configure (/opt/zigbee2mqtt/lib/extension/configure.ts:117:13))

@dennismaxjung
Copy link

I've commited a changed version to the zigbee2mqtt repo. There should be out of the box support since the last release.
So no converter should be needed

Screenshot_20230812-152036.png

@DeeBee84
Copy link

Ok, but without the external converter i have the same Problem. Error "UNSUPPORTED_ATTRIBUTE" after pairing and no humidity values in Z2M.
2023-08-13 08_50_56-Zigbee2MQTT

@FabiJa
Copy link

FabiJa commented Aug 13, 2023

I had the same problem all the time. I found a working solution: Do a manual poll in the corresponding development console tab of the device and see if it works:
Screenshot_20230813_093612_Edge

@DeeBee84
Copy link

Reading the humidity value from development console works. But it just a one-time read. The value is not updated automatically.

@FabiJa
Copy link

FabiJa commented Aug 13, 2023

Did you check the update config?
del

I think it was misconfigured in my case.

@DeeBee84
Copy link

I have the same settings. Should be the stock settings. After click on apply the humidity will be updated once. But still not automatically.

@Fr33GitHub
Copy link

In my case the humidity works after set the specific settings for humidity precision and calibration.
Bosch

@habitoti
Copy link

Besides the humidity (that doesn't work for me as well, like mentioned above), I am also missing the "running_state" property. This is reported in the status json when thermostat is in system_mode "auto":

{
    "boost": "OFF",
    "child_lock": "UNLOCK",
    "display_brightness": 3,
    "display_onTime": 10,
    "display_ontime": 10,
    "last_seen": "2023-10-30T09:57:57+01:00",
    "linkquality": 54,
    "local_temperature": 21.4,
    "local_temperature_calibration": -0.5,
    "occupied_cooling_setpoint": 5,
    "occupied_heating_setpoint": 22,
    "running_state": "heat",          <=========
    "setpoint_change_source": "externally",
    "system_mode": "auto",
    "voltage": 6300,
    "window_open": "OFF"
}

This is actually the most interesting value, as it decides whether heat needs to be provisioned or not for that room. I could decide that with an automation of course myself, but it's on the other side the whole point of using a thermostat (as it also includes warming hysteresis etc.).

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 stale Stale issues
Projects
None yet
Development

No branches or pull requests

9 participants