zigbee-herdsman-converters 25.44.0 → 25.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/devices/bosch.d.ts.map +1 -1
- package/dist/devices/bosch.js +64 -514
- package/dist/devices/bosch.js.map +1 -1
- package/dist/devices/box.d.ts +3 -0
- package/dist/devices/box.d.ts.map +1 -0
- package/dist/devices/box.js +370 -0
- package/dist/devices/box.js.map +1 -0
- package/dist/devices/elko.js +4 -4
- package/dist/devices/elko.js.map +1 -1
- package/dist/devices/gewiss.js +4 -4
- package/dist/devices/gewiss.js.map +1 -1
- package/dist/devices/index.d.ts.map +1 -1
- package/dist/devices/index.js +2 -0
- package/dist/devices/index.js.map +1 -1
- package/dist/devices/innr.d.ts.map +1 -1
- package/dist/devices/innr.js +8 -1
- package/dist/devices/innr.js.map +1 -1
- package/dist/devices/lumi.d.ts.map +1 -1
- package/dist/devices/lumi.js +13 -6
- package/dist/devices/lumi.js.map +1 -1
- package/dist/devices/nordtronic.d.ts.map +1 -1
- package/dist/devices/nordtronic.js +9 -0
- package/dist/devices/nordtronic.js.map +1 -1
- package/dist/devices/paulmann.d.ts.map +1 -1
- package/dist/devices/paulmann.js +11 -4
- package/dist/devices/paulmann.js.map +1 -1
- package/dist/devices/philips.d.ts.map +1 -1
- package/dist/devices/philips.js +6 -9
- package/dist/devices/philips.js.map +1 -1
- package/dist/devices/sengled.js +1 -1
- package/dist/devices/sengled.js.map +1 -1
- package/dist/devices/sonoff.d.ts.map +1 -1
- package/dist/devices/sonoff.js +9 -0
- package/dist/devices/sonoff.js.map +1 -1
- package/dist/devices/third_reality.d.ts.map +1 -1
- package/dist/devices/third_reality.js +23 -2
- package/dist/devices/third_reality.js.map +1 -1
- package/dist/devices/tuya.d.ts.map +1 -1
- package/dist/devices/tuya.js +279 -6
- package/dist/devices/tuya.js.map +1 -1
- package/dist/devices/wirenboard.js +1 -1
- package/dist/devices/wirenboard.js.map +1 -1
- package/dist/lib/bosch.d.ts +107 -3
- package/dist/lib/bosch.d.ts.map +1 -1
- package/dist/lib/bosch.js +757 -26
- package/dist/lib/bosch.js.map +1 -1
- package/dist/lib/exposes.d.ts +0 -1
- package/dist/lib/exposes.d.ts.map +1 -1
- package/dist/lib/exposes.js +4 -8
- package/dist/lib/exposes.js.map +1 -1
- package/dist/lib/generateDefinition.js +2 -2
- package/dist/lib/generateDefinition.js.map +1 -1
- package/dist/lib/modernExtend.d.ts +28 -10
- package/dist/lib/modernExtend.d.ts.map +1 -1
- package/dist/lib/modernExtend.js +71 -24
- package/dist/lib/modernExtend.js.map +1 -1
- package/dist/lib/tuya.d.ts.map +1 -1
- package/dist/lib/tuya.js +2 -2
- package/dist/lib/tuya.js.map +1 -1
- package/dist/lib/utils.js +3 -3
- package/dist/lib/utils.js.map +1 -1
- package/dist/models-index.json +1 -1
- package/package.json +4 -4
package/dist/lib/bosch.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.boschSmartPlugExtend = exports.boschBsenExtend = exports.boschDoorWindowContactExtend = exports.boschBsirExtend = exports.boschBmctExtend = exports.boschGeneralExtend = exports.manufacturerOptions = void 0;
|
|
36
|
+
exports.boschThermostatExtend = exports.boschSmartPlugExtend = exports.boschBsenExtend = exports.boschDoorWindowContactExtend = exports.boschBsirExtend = exports.boschBmctExtend = exports.boschGeneralExtend = exports.manufacturerOptions = void 0;
|
|
37
37
|
const zigbee_herdsman_1 = require("zigbee-herdsman");
|
|
38
38
|
const fz = __importStar(require("../converters/fromZigbee"));
|
|
39
39
|
const tz = __importStar(require("../converters/toZigbee"));
|
|
@@ -98,10 +98,20 @@ exports.boschGeneralExtend = {
|
|
|
98
98
|
isModernExtend: true,
|
|
99
99
|
};
|
|
100
100
|
},
|
|
101
|
+
/** Some Bosch devices ask the coordinator for their ZCL version
|
|
102
|
+
* during deviceAnnouncement. Without answer, these devices regularly
|
|
103
|
+
* re-join the network. To avoid that, we have to make sure that a readRequest
|
|
104
|
+
* for the zclVersion is always being answered. The answered zclVersion is
|
|
105
|
+
* taken from the Bosch Smart Home Controller II.
|
|
106
|
+
*
|
|
107
|
+
* Exception: BTH-RM and BTH-RM230Z ask the coordinator at regular
|
|
108
|
+
* intervals for their zclVersion (maybe availability check like Z2M does?)
|
|
109
|
+
* and *not* during interview! To avoid code-duplication, we handle that
|
|
110
|
+
* case here as well. */
|
|
101
111
|
handleZclVersionReadRequest: () => {
|
|
102
112
|
const onEvent = [
|
|
103
113
|
(event) => {
|
|
104
|
-
if (event.type !== "
|
|
114
|
+
if (event.type !== "start") {
|
|
105
115
|
return;
|
|
106
116
|
}
|
|
107
117
|
event.data.device.customReadResponse = (frame, endpoint) => {
|
|
@@ -124,7 +134,7 @@ exports.boschGeneralExtend = {
|
|
|
124
134
|
isModernExtend: true,
|
|
125
135
|
};
|
|
126
136
|
},
|
|
127
|
-
|
|
137
|
+
customMeteringCluster: () => m.deviceAddCustomCluster("seMetering", {
|
|
128
138
|
ID: zigbee_herdsman_1.Zcl.Clusters.seMetering.ID,
|
|
129
139
|
attributes: {},
|
|
130
140
|
commands: {
|
|
@@ -156,6 +166,13 @@ exports.boschGeneralExtend = {
|
|
|
156
166
|
isModernExtend: true,
|
|
157
167
|
};
|
|
158
168
|
},
|
|
169
|
+
batteryWithPercentageAndLowStatus: (args) => m.battery({
|
|
170
|
+
percentage: true,
|
|
171
|
+
percentageReportingConfig: false,
|
|
172
|
+
lowStatus: true,
|
|
173
|
+
lowStatusReportingConfig: { min: "MIN", max: "MAX", change: null },
|
|
174
|
+
...args,
|
|
175
|
+
}),
|
|
159
176
|
autoOff: (args) => {
|
|
160
177
|
const { endpoint } = args ?? {};
|
|
161
178
|
const offOnLookup = {
|
|
@@ -1183,20 +1200,6 @@ exports.boschBsirExtend = {
|
|
|
1183
1200
|
},
|
|
1184
1201
|
access: "STATE_GET",
|
|
1185
1202
|
}),
|
|
1186
|
-
battery: () => m.battery({
|
|
1187
|
-
percentage: true,
|
|
1188
|
-
percentageReportingConfig: {
|
|
1189
|
-
min: "MIN",
|
|
1190
|
-
max: "MAX",
|
|
1191
|
-
change: 1,
|
|
1192
|
-
},
|
|
1193
|
-
lowStatus: true,
|
|
1194
|
-
lowStatusReportingConfig: {
|
|
1195
|
-
min: "MIN",
|
|
1196
|
-
max: "MAX",
|
|
1197
|
-
change: 0,
|
|
1198
|
-
},
|
|
1199
|
-
}),
|
|
1200
1203
|
lightDelay: () => m.numeric({
|
|
1201
1204
|
name: "light_delay",
|
|
1202
1205
|
cluster: "ssIasWd",
|
|
@@ -1415,11 +1418,6 @@ exports.boschDoorWindowContactExtend = {
|
|
|
1415
1418
|
commands: {},
|
|
1416
1419
|
commandsResponse: {},
|
|
1417
1420
|
}),
|
|
1418
|
-
battery: () => m.battery({
|
|
1419
|
-
percentage: true,
|
|
1420
|
-
lowStatus: true,
|
|
1421
|
-
lowStatusReportingConfig: { min: "MIN", max: "MAX", change: 0 },
|
|
1422
|
-
}),
|
|
1423
1421
|
reportContactState: () => m.iasZoneAlarm({
|
|
1424
1422
|
zoneType: "contact",
|
|
1425
1423
|
zoneAttributes: ["alarm_1"],
|
|
@@ -1665,9 +1663,12 @@ exports.boschDoorWindowContactExtend = {
|
|
|
1665
1663
|
// Bosch Smart Home Controller II as of 19-09-2025. Looks like
|
|
1666
1664
|
// the default value was too high, and they didn't want to
|
|
1667
1665
|
// push a firmware update. We mimic it here to avoid complaints.
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1666
|
+
if (device.meta.newDefaultSensitivityApplied === undefined) {
|
|
1667
|
+
await endpoint.write("boschDoorWindowContactCluster", {
|
|
1668
|
+
vibrationDetectionSensitivity: vibrationDetectionSensitivityLookup.medium,
|
|
1669
|
+
});
|
|
1670
|
+
device.meta.newDefaultSensitivityApplied = true;
|
|
1671
|
+
}
|
|
1671
1672
|
// The write request is made when using the proprietary
|
|
1672
1673
|
// Bosch Smart Home Controller II as of 19-09-2025. I have
|
|
1673
1674
|
// no idea what it does, but we mimic it here in case it
|
|
@@ -1709,7 +1710,7 @@ exports.boschBsenExtend = {
|
|
|
1709
1710
|
voltageReporting: true,
|
|
1710
1711
|
voltageToPercentage: { min: 2500, max: 3000 },
|
|
1711
1712
|
lowStatus: true,
|
|
1712
|
-
lowStatusReportingConfig: { min: "MIN", max: "MAX", change:
|
|
1713
|
+
lowStatusReportingConfig: { min: "MIN", max: "MAX", change: null },
|
|
1713
1714
|
}),
|
|
1714
1715
|
illuminance: () => m.illuminance({ reporting: { min: "1_SECOND", max: 600, change: 3522 } }),
|
|
1715
1716
|
// The temperature sensor isn't used at all by Bosch on the BSEN-M.
|
|
@@ -2065,5 +2066,735 @@ exports.boschSmartPlugExtend = {
|
|
|
2065
2066
|
...args,
|
|
2066
2067
|
}),
|
|
2067
2068
|
};
|
|
2069
|
+
const boschThermostatLookup = {
|
|
2070
|
+
systemModes: {
|
|
2071
|
+
heat: 0x04,
|
|
2072
|
+
cool: 0x03,
|
|
2073
|
+
},
|
|
2074
|
+
raRunningStates: ["idle", "heat"],
|
|
2075
|
+
heaterType: {
|
|
2076
|
+
underfloor_heating: 0x00,
|
|
2077
|
+
radiator: 0x02,
|
|
2078
|
+
central_heating: 0x01,
|
|
2079
|
+
manual_control: 0x03,
|
|
2080
|
+
},
|
|
2081
|
+
};
|
|
2082
|
+
exports.boschThermostatExtend = {
|
|
2083
|
+
customThermostatCluster: () => m.deviceAddCustomCluster("hvacThermostat", {
|
|
2084
|
+
ID: zigbee_herdsman_1.Zcl.Clusters.hvacThermostat.ID,
|
|
2085
|
+
attributes: {
|
|
2086
|
+
operatingMode: { ID: 0x4007, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2087
|
+
heatingDemand: { ID: 0x4020, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2088
|
+
valveAdaptStatus: { ID: 0x4022, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2089
|
+
unknownAttribute0: { ID: 0x4025, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2090
|
+
remoteTemperature: { ID: 0x4040, type: zigbee_herdsman_1.Zcl.DataType.INT16, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2091
|
+
unknownAttribute1: { ID: 0x4041, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2092
|
+
windowOpenMode: { ID: 0x4042, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2093
|
+
boostHeating: { ID: 0x4043, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2094
|
+
cableSensorTemperature: { ID: 0x4052, type: zigbee_herdsman_1.Zcl.DataType.INT16, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2095
|
+
valveType: { ID: 0x4060, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2096
|
+
unknownAttribute2: { ID: 0x4061, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2097
|
+
cableSensorMode: { ID: 0x4062, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2098
|
+
heaterType: { ID: 0x4063, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2099
|
+
errorState: { ID: 0x5000, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2100
|
+
automaticValveAdapt: { ID: 0x5010, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2101
|
+
},
|
|
2102
|
+
commands: {
|
|
2103
|
+
calibrateValve: { ID: 0x41, parameters: [] },
|
|
2104
|
+
},
|
|
2105
|
+
commandsResponse: {},
|
|
2106
|
+
}),
|
|
2107
|
+
customUserInterfaceCfgCluster: () => m.deviceAddCustomCluster("hvacUserInterfaceCfg", {
|
|
2108
|
+
ID: zigbee_herdsman_1.Zcl.Clusters.hvacUserInterfaceCfg.ID,
|
|
2109
|
+
attributes: {
|
|
2110
|
+
displayOrientation: { ID: 0x400b, type: zigbee_herdsman_1.Zcl.DataType.UINT8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2111
|
+
activityLed: { ID: 0x4033, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2112
|
+
displayedTemperature: { ID: 0x4039, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2113
|
+
displaySwitchOnDuration: { ID: 0x403a, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2114
|
+
displayBrightness: { ID: 0x403b, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: exports.manufacturerOptions.manufacturerCode },
|
|
2115
|
+
},
|
|
2116
|
+
commands: {},
|
|
2117
|
+
commandsResponse: {},
|
|
2118
|
+
}),
|
|
2119
|
+
relayState: () => m.onOff({ description: "The state of the relay controlling the connected heating/cooling device", powerOnBehavior: false }),
|
|
2120
|
+
cableSensorMode: () => m.enumLookup({
|
|
2121
|
+
name: "cable_sensor_mode",
|
|
2122
|
+
cluster: "hvacThermostat",
|
|
2123
|
+
attribute: "cableSensorMode",
|
|
2124
|
+
description: 'Select a configuration for the sensor connection. If you select "with_regulation", ' +
|
|
2125
|
+
"the measured temperature on the cable sensor is used by the heating/cooling algorithm " +
|
|
2126
|
+
"instead of the local temperature.",
|
|
2127
|
+
lookup: { not_used: 0x00, cable_sensor_without_regulation: 0xb0, cable_sensor_with_regulation: 0xb1 },
|
|
2128
|
+
reporting: false,
|
|
2129
|
+
entityCategory: "config",
|
|
2130
|
+
}),
|
|
2131
|
+
cableSensorTemperature: () => m.numeric({
|
|
2132
|
+
name: "cable_sensor_temperature",
|
|
2133
|
+
cluster: "hvacThermostat",
|
|
2134
|
+
attribute: "cableSensorTemperature",
|
|
2135
|
+
description: "Measured temperature value on the cable sensor (if enabled)",
|
|
2136
|
+
unit: "°C",
|
|
2137
|
+
scale: 100,
|
|
2138
|
+
reporting: { min: 30, max: "MAX", change: 20 },
|
|
2139
|
+
access: "STATE_GET",
|
|
2140
|
+
}),
|
|
2141
|
+
heaterType: () => m.enumLookup({
|
|
2142
|
+
name: "heater_type",
|
|
2143
|
+
cluster: "hvacThermostat",
|
|
2144
|
+
attribute: "heaterType",
|
|
2145
|
+
description: "Select the connected heater type or 'manual_control' if you like to activate the relay manually when necessary",
|
|
2146
|
+
lookup: boschThermostatLookup.heaterType,
|
|
2147
|
+
reporting: false,
|
|
2148
|
+
entityCategory: "config",
|
|
2149
|
+
}),
|
|
2150
|
+
valveType: () => m.enumLookup({
|
|
2151
|
+
name: "valve_type",
|
|
2152
|
+
cluster: "hvacThermostat",
|
|
2153
|
+
attribute: "valveType",
|
|
2154
|
+
description: "Select the connected valve type",
|
|
2155
|
+
lookup: { normally_closed: 0x00, normally_open: 0x01 },
|
|
2156
|
+
reporting: false,
|
|
2157
|
+
entityCategory: "config",
|
|
2158
|
+
}),
|
|
2159
|
+
humidity: () => m.humidity({ reporting: false }),
|
|
2160
|
+
windowOpenMode: (args) => m.binary({
|
|
2161
|
+
name: "window_open_mode",
|
|
2162
|
+
cluster: "hvacThermostat",
|
|
2163
|
+
attribute: "windowOpenMode",
|
|
2164
|
+
description: "Activates the window open mode, where the thermostat disables any heating/cooling " +
|
|
2165
|
+
"to prevent unnecessary energy consumption. Please keep in mind that the device " +
|
|
2166
|
+
"itself does not detect any open windows!",
|
|
2167
|
+
valueOn: ["ON", 0x01],
|
|
2168
|
+
valueOff: ["OFF", 0x00],
|
|
2169
|
+
reporting: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
|
|
2170
|
+
}),
|
|
2171
|
+
childLock: () => m.binary({
|
|
2172
|
+
name: "child_lock",
|
|
2173
|
+
cluster: "hvacUserInterfaceCfg",
|
|
2174
|
+
attribute: "keypadLockout",
|
|
2175
|
+
description: "Enables/disables physical input on the thermostat",
|
|
2176
|
+
valueOn: ["LOCK", 0x01],
|
|
2177
|
+
valueOff: ["UNLOCK", 0x00],
|
|
2178
|
+
reporting: { min: "MIN", max: "MAX", change: null },
|
|
2179
|
+
}),
|
|
2180
|
+
displayBrightness: () => m.numeric({
|
|
2181
|
+
name: "display_brightness",
|
|
2182
|
+
cluster: "hvacUserInterfaceCfg",
|
|
2183
|
+
attribute: "displayBrightness",
|
|
2184
|
+
description: "Sets brightness of the display",
|
|
2185
|
+
valueMin: 0,
|
|
2186
|
+
valueMax: 100,
|
|
2187
|
+
valueStep: 10,
|
|
2188
|
+
unit: "%",
|
|
2189
|
+
scale: 0.1,
|
|
2190
|
+
reporting: false,
|
|
2191
|
+
entityCategory: "config",
|
|
2192
|
+
}),
|
|
2193
|
+
displaySwitchOnDuration: () => m.numeric({
|
|
2194
|
+
name: "display_switch_on_duration",
|
|
2195
|
+
cluster: "hvacUserInterfaceCfg",
|
|
2196
|
+
attribute: "displaySwitchOnDuration",
|
|
2197
|
+
label: "Display switch-on duration",
|
|
2198
|
+
description: "Sets the time before the display is automatically switched off",
|
|
2199
|
+
valueMin: 5,
|
|
2200
|
+
valueMax: 30,
|
|
2201
|
+
unit: "s",
|
|
2202
|
+
reporting: false,
|
|
2203
|
+
entityCategory: "config",
|
|
2204
|
+
}),
|
|
2205
|
+
displayOrientation: () => m.enumLookup({
|
|
2206
|
+
name: "display_orientation",
|
|
2207
|
+
cluster: "hvacUserInterfaceCfg",
|
|
2208
|
+
attribute: "displayOrientation",
|
|
2209
|
+
description: "You can rotate the display content by 180° here. This is recommended if your thermostat is fitted vertically, for instance.",
|
|
2210
|
+
lookup: { standard_arrangement: 0x00, rotated_by_180_degrees: 0x01 },
|
|
2211
|
+
reporting: false,
|
|
2212
|
+
entityCategory: "config",
|
|
2213
|
+
}),
|
|
2214
|
+
displayedTemperature: () => m.enumLookup({
|
|
2215
|
+
name: "displayed_temperature",
|
|
2216
|
+
cluster: "hvacUserInterfaceCfg",
|
|
2217
|
+
attribute: "displayedTemperature",
|
|
2218
|
+
description: "Select which temperature should be displayed on your radiator thermostat display",
|
|
2219
|
+
lookup: { set_temperature: 0x00, measured_temperature: 0x01 },
|
|
2220
|
+
reporting: false,
|
|
2221
|
+
entityCategory: "config",
|
|
2222
|
+
}),
|
|
2223
|
+
activityLedState: () => m.enumLookup({
|
|
2224
|
+
name: "activity_led",
|
|
2225
|
+
cluster: "hvacUserInterfaceCfg",
|
|
2226
|
+
attribute: "activityLed",
|
|
2227
|
+
label: "Activity LED state",
|
|
2228
|
+
description: "Determines the state of the little dot on the display next to the heating/cooling symbol",
|
|
2229
|
+
lookup: { off: 0x00, auto: 0x01, on: 0x02 },
|
|
2230
|
+
reporting: false,
|
|
2231
|
+
entityCategory: "config",
|
|
2232
|
+
}),
|
|
2233
|
+
remoteTemperature: () => m.numeric({
|
|
2234
|
+
name: "remote_temperature",
|
|
2235
|
+
cluster: "hvacThermostat",
|
|
2236
|
+
attribute: "remoteTemperature",
|
|
2237
|
+
description: "Input for remote temperature sensor. Required at least every 30 minutes to prevent fallback to the internal sensor!",
|
|
2238
|
+
valueMin: 0.0,
|
|
2239
|
+
valueMax: 35.0,
|
|
2240
|
+
valueStep: 0.2,
|
|
2241
|
+
unit: "°C",
|
|
2242
|
+
scale: 100,
|
|
2243
|
+
reporting: false,
|
|
2244
|
+
entityCategory: "config",
|
|
2245
|
+
}),
|
|
2246
|
+
setpointChangeSource: (args) => m.enumLookup({
|
|
2247
|
+
name: "setpoint_change_source",
|
|
2248
|
+
cluster: "hvacThermostat",
|
|
2249
|
+
attribute: "setpointChangeSource",
|
|
2250
|
+
description: "Source of the current setpoint temperature",
|
|
2251
|
+
lookup: { manual: 0x00, schedule: 0x01, externally: 0x02 },
|
|
2252
|
+
access: "STATE_GET",
|
|
2253
|
+
reporting: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
|
|
2254
|
+
entityCategory: "diagnostic",
|
|
2255
|
+
}),
|
|
2256
|
+
customHeatingDemand: () => m.numeric({
|
|
2257
|
+
name: "pi_heating_demand",
|
|
2258
|
+
cluster: "hvacThermostat",
|
|
2259
|
+
attribute: "heatingDemand",
|
|
2260
|
+
label: "PI heating demand",
|
|
2261
|
+
description: "Position of the valve (= demanded heat) where 0% is fully closed and 100% is fully open",
|
|
2262
|
+
unit: "%",
|
|
2263
|
+
valueMin: 0,
|
|
2264
|
+
valueMax: 100,
|
|
2265
|
+
access: "ALL",
|
|
2266
|
+
reporting: { min: "MIN", max: "MAX", change: null },
|
|
2267
|
+
}),
|
|
2268
|
+
rmBattery: () => m.battery({
|
|
2269
|
+
percentage: true,
|
|
2270
|
+
percentageReporting: false,
|
|
2271
|
+
voltage: true,
|
|
2272
|
+
voltageReporting: true,
|
|
2273
|
+
voltageReportingConfig: false,
|
|
2274
|
+
voltageToPercentage: { min: 4400, max: 6400 },
|
|
2275
|
+
lowStatus: true,
|
|
2276
|
+
lowStatusReportingConfig: { min: "MIN", max: "MAX", change: null },
|
|
2277
|
+
}),
|
|
2278
|
+
rmThermostat: () => {
|
|
2279
|
+
const thermostat = m.thermostat({
|
|
2280
|
+
localTemperature: {
|
|
2281
|
+
configure: { reporting: false },
|
|
2282
|
+
},
|
|
2283
|
+
localTemperatureCalibration: {
|
|
2284
|
+
values: { min: -5, max: 5, step: 0.1 },
|
|
2285
|
+
configure: { reporting: false },
|
|
2286
|
+
},
|
|
2287
|
+
setpoints: {
|
|
2288
|
+
values: {
|
|
2289
|
+
occupiedHeatingSetpoint: { min: 5, max: 30, step: 0.5 },
|
|
2290
|
+
occupiedCoolingSetpoint: { min: 5, max: 30, step: 0.5 },
|
|
2291
|
+
},
|
|
2292
|
+
configure: { reporting: false },
|
|
2293
|
+
},
|
|
2294
|
+
systemMode: {
|
|
2295
|
+
values: ["heat", "cool"],
|
|
2296
|
+
toZigbee: { skip: true },
|
|
2297
|
+
configure: { skip: true },
|
|
2298
|
+
},
|
|
2299
|
+
runningState: {
|
|
2300
|
+
values: ["idle", "heat", "cool"],
|
|
2301
|
+
configure: { reporting: false },
|
|
2302
|
+
},
|
|
2303
|
+
});
|
|
2304
|
+
const expose = (device, options) => {
|
|
2305
|
+
const returnedThermostat = thermostat.exposes;
|
|
2306
|
+
if (utils.isDummyDevice(device)) {
|
|
2307
|
+
return returnedThermostat;
|
|
2308
|
+
}
|
|
2309
|
+
let currentSystemMode;
|
|
2310
|
+
try {
|
|
2311
|
+
currentSystemMode = utils.getFromLookupByValue(device.getEndpoint(1).getClusterAttributeValue("hvacThermostat", "systemMode"), boschThermostatLookup.systemModes);
|
|
2312
|
+
}
|
|
2313
|
+
catch {
|
|
2314
|
+
currentSystemMode = "heat";
|
|
2315
|
+
}
|
|
2316
|
+
// The thermostat is a singleton, thus the values must be set
|
|
2317
|
+
// manually as filtering will lead to an array without
|
|
2318
|
+
// heat/cool in them after two systemMode changes.
|
|
2319
|
+
returnedThermostat[0].features.forEach((exposedAttribute, index, array) => {
|
|
2320
|
+
if (exposedAttribute.type === "enum") {
|
|
2321
|
+
if (exposedAttribute.name === "system_mode") {
|
|
2322
|
+
exposedAttribute.label = "Active system mode";
|
|
2323
|
+
exposedAttribute.description =
|
|
2324
|
+
"Currently used system mode by the thermostat. This field is primarily " +
|
|
2325
|
+
"used to configure the thermostat in Home Assistant correctly.";
|
|
2326
|
+
exposedAttribute.values = [currentSystemMode];
|
|
2327
|
+
exposedAttribute.access = ea.STATE;
|
|
2328
|
+
}
|
|
2329
|
+
if (exposedAttribute.name === "running_state") {
|
|
2330
|
+
exposedAttribute.values = ["idle", currentSystemMode];
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
});
|
|
2334
|
+
return returnedThermostat;
|
|
2335
|
+
};
|
|
2336
|
+
return {
|
|
2337
|
+
exposes: [expose],
|
|
2338
|
+
fromZigbee: thermostat.fromZigbee,
|
|
2339
|
+
toZigbee: thermostat.toZigbee,
|
|
2340
|
+
configure: thermostat.configure,
|
|
2341
|
+
isModernExtend: true,
|
|
2342
|
+
};
|
|
2343
|
+
},
|
|
2344
|
+
customSystemMode: () => {
|
|
2345
|
+
const exposes = [
|
|
2346
|
+
e
|
|
2347
|
+
.enum("custom_system_mode", ea.ALL, Object.keys(boschThermostatLookup.systemModes))
|
|
2348
|
+
.withLabel("Available system modes")
|
|
2349
|
+
.withDescription("Select if the thermostat is connected to a heating or a cooling device")
|
|
2350
|
+
.withCategory("config"),
|
|
2351
|
+
];
|
|
2352
|
+
const fromZigbee = [
|
|
2353
|
+
{
|
|
2354
|
+
cluster: "hvacThermostat",
|
|
2355
|
+
type: ["attributeReport", "readResponse"],
|
|
2356
|
+
convert: (model, msg, publish, options, meta) => {
|
|
2357
|
+
const result = {};
|
|
2358
|
+
const data = msg.data;
|
|
2359
|
+
if (data.systemMode !== undefined) {
|
|
2360
|
+
result.custom_system_mode = utils.getFromLookupByValue(data.systemMode, boschThermostatLookup.systemModes);
|
|
2361
|
+
meta.deviceExposesChanged();
|
|
2362
|
+
}
|
|
2363
|
+
return result;
|
|
2364
|
+
},
|
|
2365
|
+
},
|
|
2366
|
+
];
|
|
2367
|
+
const toZigbee = [
|
|
2368
|
+
{
|
|
2369
|
+
key: ["custom_system_mode"],
|
|
2370
|
+
convertSet: async (entity, key, value, meta) => {
|
|
2371
|
+
await entity.write("hvacThermostat", {
|
|
2372
|
+
systemMode: utils.toNumber(utils.getFromLookup(value, boschThermostatLookup.systemModes)),
|
|
2373
|
+
});
|
|
2374
|
+
return { state: { custom_system_mode: value } };
|
|
2375
|
+
},
|
|
2376
|
+
convertGet: async (entity, key, meta) => {
|
|
2377
|
+
await entity.read("hvacThermostat", ["systemMode"]);
|
|
2378
|
+
},
|
|
2379
|
+
},
|
|
2380
|
+
];
|
|
2381
|
+
const configure = [
|
|
2382
|
+
m.setupConfigureForReporting("hvacThermostat", "systemMode", {
|
|
2383
|
+
config: false,
|
|
2384
|
+
access: ea.ALL,
|
|
2385
|
+
}),
|
|
2386
|
+
];
|
|
2387
|
+
return {
|
|
2388
|
+
exposes,
|
|
2389
|
+
fromZigbee,
|
|
2390
|
+
toZigbee,
|
|
2391
|
+
configure,
|
|
2392
|
+
isModernExtend: true,
|
|
2393
|
+
};
|
|
2394
|
+
},
|
|
2395
|
+
raThermostat: () => {
|
|
2396
|
+
// Native thermostat
|
|
2397
|
+
const thermostat = m.thermostat({
|
|
2398
|
+
localTemperature: {
|
|
2399
|
+
values: {
|
|
2400
|
+
description: "Temperature used by the heating algorithm. This is the " +
|
|
2401
|
+
"temperature measured on the device (by default) or the " +
|
|
2402
|
+
"remote temperature (if set within the last 30 min).",
|
|
2403
|
+
},
|
|
2404
|
+
configure: {
|
|
2405
|
+
reporting: { min: 30, max: 900, change: 20 },
|
|
2406
|
+
},
|
|
2407
|
+
},
|
|
2408
|
+
localTemperatureCalibration: {
|
|
2409
|
+
values: { min: -5, max: 5, step: 0.1 },
|
|
2410
|
+
configure: { reporting: false },
|
|
2411
|
+
},
|
|
2412
|
+
setpoints: {
|
|
2413
|
+
values: {
|
|
2414
|
+
occupiedHeatingSetpoint: { min: 5, max: 30, step: 0.5 },
|
|
2415
|
+
},
|
|
2416
|
+
configure: {
|
|
2417
|
+
reporting: { min: "MIN", max: "MAX", change: 1 },
|
|
2418
|
+
},
|
|
2419
|
+
},
|
|
2420
|
+
systemMode: {
|
|
2421
|
+
values: ["heat"],
|
|
2422
|
+
configure: {
|
|
2423
|
+
reporting: false,
|
|
2424
|
+
},
|
|
2425
|
+
},
|
|
2426
|
+
runningState: {
|
|
2427
|
+
values: boschThermostatLookup.raRunningStates,
|
|
2428
|
+
toZigbee: {
|
|
2429
|
+
skip: true,
|
|
2430
|
+
},
|
|
2431
|
+
configure: {
|
|
2432
|
+
reporting: false,
|
|
2433
|
+
},
|
|
2434
|
+
},
|
|
2435
|
+
piHeatingDemand: {
|
|
2436
|
+
values: ea.ALL,
|
|
2437
|
+
toZigbee: {
|
|
2438
|
+
skip: true,
|
|
2439
|
+
},
|
|
2440
|
+
configure: {
|
|
2441
|
+
skip: true,
|
|
2442
|
+
},
|
|
2443
|
+
},
|
|
2444
|
+
});
|
|
2445
|
+
const exposes = thermostat.exposes;
|
|
2446
|
+
const fromZigbee = thermostat.fromZigbee;
|
|
2447
|
+
const toZigbee = thermostat.toZigbee;
|
|
2448
|
+
let configure = thermostat.configure;
|
|
2449
|
+
// Add converters for custom running state
|
|
2450
|
+
const runningState = exports.boschThermostatExtend.customRunningState();
|
|
2451
|
+
fromZigbee.push(...runningState.fromZigbee);
|
|
2452
|
+
toZigbee.push(...runningState.toZigbee);
|
|
2453
|
+
// Add converters and configure for custom heating demand
|
|
2454
|
+
const piHeatingDemand = exports.boschThermostatExtend.customHeatingDemand();
|
|
2455
|
+
fromZigbee.push(...piHeatingDemand.fromZigbee);
|
|
2456
|
+
toZigbee.push(...piHeatingDemand.toZigbee);
|
|
2457
|
+
configure = [...configure, ...piHeatingDemand.configure];
|
|
2458
|
+
return {
|
|
2459
|
+
exposes,
|
|
2460
|
+
fromZigbee,
|
|
2461
|
+
toZigbee,
|
|
2462
|
+
configure,
|
|
2463
|
+
isModernExtend: true,
|
|
2464
|
+
};
|
|
2465
|
+
},
|
|
2466
|
+
customRunningState: () => {
|
|
2467
|
+
const fromZigbee = [
|
|
2468
|
+
{
|
|
2469
|
+
cluster: "hvacThermostat",
|
|
2470
|
+
type: ["attributeReport", "readResponse"],
|
|
2471
|
+
convert: (model, msg, publish, options, meta) => {
|
|
2472
|
+
const result = {};
|
|
2473
|
+
const data = msg.data;
|
|
2474
|
+
if (data.heatingDemand !== undefined) {
|
|
2475
|
+
result.running_state =
|
|
2476
|
+
utils.toNumber(data.heatingDemand) > 0
|
|
2477
|
+
? boschThermostatLookup.raRunningStates[1]
|
|
2478
|
+
: boschThermostatLookup.raRunningStates[0];
|
|
2479
|
+
}
|
|
2480
|
+
return result;
|
|
2481
|
+
},
|
|
2482
|
+
},
|
|
2483
|
+
];
|
|
2484
|
+
const toZigbee = [
|
|
2485
|
+
{
|
|
2486
|
+
key: ["running_state"],
|
|
2487
|
+
convertGet: async (entity, key, meta) => {
|
|
2488
|
+
await entity.read("hvacThermostat", ["heatingDemand"]);
|
|
2489
|
+
},
|
|
2490
|
+
},
|
|
2491
|
+
];
|
|
2492
|
+
return {
|
|
2493
|
+
fromZigbee,
|
|
2494
|
+
toZigbee,
|
|
2495
|
+
isModernExtend: true,
|
|
2496
|
+
};
|
|
2497
|
+
},
|
|
2498
|
+
operatingMode: (args) => {
|
|
2499
|
+
const operatingModeLookup = { schedule: 0x00, manual: 0x01, pause: 0x05 };
|
|
2500
|
+
const operatingMode = m.enumLookup({
|
|
2501
|
+
name: "operating_mode",
|
|
2502
|
+
cluster: "hvacThermostat",
|
|
2503
|
+
attribute: "operatingMode",
|
|
2504
|
+
description: "Bosch-specific operating mode. This is being used as mode on the exposed thermostat when using Home Assistant.",
|
|
2505
|
+
lookup: operatingModeLookup,
|
|
2506
|
+
reporting: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
|
|
2507
|
+
entityCategory: "config",
|
|
2508
|
+
});
|
|
2509
|
+
const exposes = operatingMode.exposes;
|
|
2510
|
+
const fromZigbee = operatingMode.fromZigbee;
|
|
2511
|
+
const toZigbee = operatingMode.toZigbee;
|
|
2512
|
+
const configure = operatingMode.configure;
|
|
2513
|
+
const removeLowAndHighTemperatureFields = (payload) => {
|
|
2514
|
+
payload.temperature_high_command_topic = undefined;
|
|
2515
|
+
payload.temperature_low_command_topic = undefined;
|
|
2516
|
+
payload.temperature_high_state_template = undefined;
|
|
2517
|
+
payload.temperature_low_state_template = undefined;
|
|
2518
|
+
payload.temperature_high_state_topic = undefined;
|
|
2519
|
+
payload.temperature_low_state_topic = undefined;
|
|
2520
|
+
};
|
|
2521
|
+
// Override the payload send to Home Assistant to achieve the following:
|
|
2522
|
+
// 1. Use the Bosch operating mode instead of system modes
|
|
2523
|
+
// See: https://github.com/Koenkk/zigbee2mqtt/pull/23075#issue-2355829475
|
|
2524
|
+
// 2. Remove setpoints not compatible with the currently used system mode
|
|
2525
|
+
// See: https://github.com/Koenkk/zigbee2mqtt/issues/28892
|
|
2526
|
+
const meta = {
|
|
2527
|
+
overrideHaDiscoveryPayload: (payload) => {
|
|
2528
|
+
if (payload.modes !== undefined) {
|
|
2529
|
+
if (payload.modes.includes("heat")) {
|
|
2530
|
+
payload.mode_command_template =
|
|
2531
|
+
`{% set values = { 'auto':'schedule', 'heat':'manual', 'off':'pause' } %}` +
|
|
2532
|
+
`{"operating_mode": "{{ values[value] if value in values.keys() else 'pause' }}"}`;
|
|
2533
|
+
payload.mode_state_template =
|
|
2534
|
+
`{% set values = { 'schedule':'auto', 'manual':'heat', 'pause':'off' } %}` +
|
|
2535
|
+
"{% set value = value_json.operating_mode %}" +
|
|
2536
|
+
`{{ values[value] if value in values.keys() else 'off' }}`;
|
|
2537
|
+
if (payload.temperature_low_command_topic !== undefined) {
|
|
2538
|
+
payload.temperature_command_topic = payload.temperature_low_command_topic;
|
|
2539
|
+
payload.temperature_state_template = payload.temperature_low_state_template;
|
|
2540
|
+
payload.temperature_state_topic = payload.temperature_low_state_topic;
|
|
2541
|
+
removeLowAndHighTemperatureFields(payload);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
else if (payload.modes.includes("cool")) {
|
|
2545
|
+
payload.mode_command_template =
|
|
2546
|
+
`{% set values = { 'auto':'schedule', 'cool':'manual', 'off':'pause' } %}` +
|
|
2547
|
+
`{"operating_mode": "{{ values[value] if value in values.keys() else 'pause' }}"}`;
|
|
2548
|
+
payload.mode_state_template =
|
|
2549
|
+
`{% set values = { 'schedule':'auto', 'manual':'cool', 'pause':'off' } %}` +
|
|
2550
|
+
"{% set value = value_json.operating_mode %}" +
|
|
2551
|
+
`{{ values[value] if value in values.keys() else 'off' }}`;
|
|
2552
|
+
if (payload.temperature_high_command_topic !== undefined) {
|
|
2553
|
+
payload.temperature_command_topic = payload.temperature_high_command_topic;
|
|
2554
|
+
payload.temperature_state_template = payload.temperature_high_state_template;
|
|
2555
|
+
payload.temperature_state_topic = payload.temperature_high_state_topic;
|
|
2556
|
+
removeLowAndHighTemperatureFields(payload);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
payload.modes = ["off", ...payload.modes, "auto"];
|
|
2560
|
+
payload.mode_command_topic = payload.mode_command_topic.replace("/system_mode", "");
|
|
2561
|
+
}
|
|
2562
|
+
},
|
|
2563
|
+
};
|
|
2564
|
+
return {
|
|
2565
|
+
exposes,
|
|
2566
|
+
fromZigbee,
|
|
2567
|
+
toZigbee,
|
|
2568
|
+
configure,
|
|
2569
|
+
meta,
|
|
2570
|
+
isModernExtend: true,
|
|
2571
|
+
};
|
|
2572
|
+
},
|
|
2573
|
+
boostHeating: (args) => {
|
|
2574
|
+
const boostHeatingLookup = {
|
|
2575
|
+
OFF: 0x00,
|
|
2576
|
+
ON: 0x01,
|
|
2577
|
+
};
|
|
2578
|
+
const exposes = [
|
|
2579
|
+
e
|
|
2580
|
+
.binary("boost_heating", ea.ALL, utils.getFromLookupByValue(0x01, boostHeatingLookup), utils.getFromLookupByValue(0x00, boostHeatingLookup))
|
|
2581
|
+
.withLabel("Activate boost heating")
|
|
2582
|
+
.withDescription("Activate boost heating (opens TRV for 5 minutes)"),
|
|
2583
|
+
];
|
|
2584
|
+
const fromZigbee = [
|
|
2585
|
+
{
|
|
2586
|
+
cluster: "hvacThermostat",
|
|
2587
|
+
type: ["attributeReport", "readResponse"],
|
|
2588
|
+
convert: (model, msg, publish, options, meta) => {
|
|
2589
|
+
const result = {};
|
|
2590
|
+
const data = msg.data;
|
|
2591
|
+
if (data.boostHeating !== undefined) {
|
|
2592
|
+
result.boost_heating = utils.getFromLookupByValue(data.boostHeating, boostHeatingLookup);
|
|
2593
|
+
}
|
|
2594
|
+
return result;
|
|
2595
|
+
},
|
|
2596
|
+
},
|
|
2597
|
+
];
|
|
2598
|
+
const toZigbee = [
|
|
2599
|
+
{
|
|
2600
|
+
key: ["boost_heating"],
|
|
2601
|
+
convertSet: async (entity, key, value, meta) => {
|
|
2602
|
+
const enableBoostHeating = value === utils.getFromLookupByValue(boostHeatingLookup.ON, boostHeatingLookup);
|
|
2603
|
+
if (enableBoostHeating) {
|
|
2604
|
+
const systemModeNotSetToHeat = "system_mode" in meta.state && meta.state.system_mode !== "heat";
|
|
2605
|
+
if (systemModeNotSetToHeat) {
|
|
2606
|
+
throw new Error("Boost heating is only possible when system mode is set to 'heat'!");
|
|
2607
|
+
}
|
|
2608
|
+
const heaterTypeNotSetToRadiator = "heater_type" in meta.state &&
|
|
2609
|
+
meta.state.heater_type !==
|
|
2610
|
+
utils.getFromLookupByValue(boschThermostatLookup.heaterType.radiator, boschThermostatLookup.heaterType);
|
|
2611
|
+
if (heaterTypeNotSetToRadiator) {
|
|
2612
|
+
throw new Error("Boost heating is only possible when heater type is set to 'radiator'!");
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
await entity.write("hvacThermostat", {
|
|
2616
|
+
boostHeating: utils.toNumber(utils.getFromLookup(value, boostHeatingLookup)),
|
|
2617
|
+
});
|
|
2618
|
+
return { state: { boost_heating: value } };
|
|
2619
|
+
},
|
|
2620
|
+
convertGet: async (entity, key, meta) => {
|
|
2621
|
+
await entity.read("hvacThermostat", ["boostHeating"]);
|
|
2622
|
+
},
|
|
2623
|
+
},
|
|
2624
|
+
];
|
|
2625
|
+
const configure = [
|
|
2626
|
+
m.setupConfigureForReporting("hvacThermostat", "boostHeating", {
|
|
2627
|
+
config: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
|
|
2628
|
+
access: ea.ALL,
|
|
2629
|
+
}),
|
|
2630
|
+
];
|
|
2631
|
+
return {
|
|
2632
|
+
exposes,
|
|
2633
|
+
fromZigbee,
|
|
2634
|
+
toZigbee,
|
|
2635
|
+
configure,
|
|
2636
|
+
isModernExtend: true,
|
|
2637
|
+
};
|
|
2638
|
+
},
|
|
2639
|
+
errorState: (args) => {
|
|
2640
|
+
const exposes = [
|
|
2641
|
+
e
|
|
2642
|
+
.text("error_state", ea.STATE_GET)
|
|
2643
|
+
.withDescription("Indicates whether the device encounters any errors or not")
|
|
2644
|
+
.withCategory("diagnostic"),
|
|
2645
|
+
];
|
|
2646
|
+
const fromZigbee = [
|
|
2647
|
+
{
|
|
2648
|
+
cluster: "hvacThermostat",
|
|
2649
|
+
type: ["attributeReport", "readResponse"],
|
|
2650
|
+
convert: (model, msg, publish, options, meta) => {
|
|
2651
|
+
const result = {};
|
|
2652
|
+
const data = msg.data;
|
|
2653
|
+
if (data.errorState !== undefined) {
|
|
2654
|
+
const receivedErrorState = data.errorState;
|
|
2655
|
+
if (receivedErrorState === 0) {
|
|
2656
|
+
result.error_state = "ok";
|
|
2657
|
+
}
|
|
2658
|
+
else {
|
|
2659
|
+
result.error_state = "";
|
|
2660
|
+
const bitmapLength = (receivedErrorState >>> 0).toString(2).length;
|
|
2661
|
+
for (let errorNumber = 0; errorNumber < bitmapLength; errorNumber++) {
|
|
2662
|
+
if ((receivedErrorState >> errorNumber) & 1) {
|
|
2663
|
+
if (String(result.error_state).length > 0) {
|
|
2664
|
+
result.error_state += " - ";
|
|
2665
|
+
}
|
|
2666
|
+
result.error_state += `E${String(errorNumber + 1).padStart(2, "0")}`;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
return result;
|
|
2672
|
+
},
|
|
2673
|
+
},
|
|
2674
|
+
];
|
|
2675
|
+
const toZigbee = [
|
|
2676
|
+
{
|
|
2677
|
+
key: ["error_state"],
|
|
2678
|
+
convertGet: async (entity, key, meta) => {
|
|
2679
|
+
await entity.read("hvacThermostat", ["errorState"]);
|
|
2680
|
+
},
|
|
2681
|
+
},
|
|
2682
|
+
];
|
|
2683
|
+
const configure = [
|
|
2684
|
+
m.setupConfigureForReporting("hvacThermostat", "errorState", {
|
|
2685
|
+
config: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
|
|
2686
|
+
access: ea.STATE_GET,
|
|
2687
|
+
}),
|
|
2688
|
+
];
|
|
2689
|
+
return {
|
|
2690
|
+
exposes,
|
|
2691
|
+
fromZigbee,
|
|
2692
|
+
toZigbee,
|
|
2693
|
+
configure,
|
|
2694
|
+
isModernExtend: true,
|
|
2695
|
+
};
|
|
2696
|
+
},
|
|
2697
|
+
valveAdaptation: () => {
|
|
2698
|
+
const valveAdaptStatusLookup = {
|
|
2699
|
+
none: 0x00,
|
|
2700
|
+
ready_to_calibrate: 0x01,
|
|
2701
|
+
calibration_in_progress: 0x02,
|
|
2702
|
+
error: 0x03,
|
|
2703
|
+
success: 0x04,
|
|
2704
|
+
};
|
|
2705
|
+
const triggerValveAdaptation = async (state, endpoint, throwError = true) => {
|
|
2706
|
+
let adaptStatus;
|
|
2707
|
+
try {
|
|
2708
|
+
adaptStatus = utils.getFromLookup(state.valve_adapt_status, valveAdaptStatusLookup);
|
|
2709
|
+
}
|
|
2710
|
+
catch {
|
|
2711
|
+
adaptStatus = valveAdaptStatusLookup.none;
|
|
2712
|
+
}
|
|
2713
|
+
switch (adaptStatus) {
|
|
2714
|
+
case valveAdaptStatusLookup.ready_to_calibrate:
|
|
2715
|
+
case valveAdaptStatusLookup.error:
|
|
2716
|
+
await endpoint.command("hvacThermostat", "calibrateValve", {}, exports.manufacturerOptions);
|
|
2717
|
+
break;
|
|
2718
|
+
default:
|
|
2719
|
+
if (throwError) {
|
|
2720
|
+
throw new Error("Valve adaptation process not possible right now!");
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
const exposes = [
|
|
2725
|
+
e
|
|
2726
|
+
.enum("valve_adapt_status", ea.STATE_GET, Object.keys(valveAdaptStatusLookup))
|
|
2727
|
+
.withLabel("Valve adaptation status")
|
|
2728
|
+
.withDescription("Specifies the current status of the valve adaptation")
|
|
2729
|
+
.withCategory("diagnostic"),
|
|
2730
|
+
e
|
|
2731
|
+
.binary("automatic_valve_adapt", ea.STATE_GET, true, false)
|
|
2732
|
+
.withLabel("Automatic valve adaptation requested")
|
|
2733
|
+
.withDescription("Specifies if an automatic valve adaptation is being requested by the thermostat " +
|
|
2734
|
+
"(for example after a successful firmware upgrade). If this is the case, the " +
|
|
2735
|
+
"valve adaptation will be automatically started as soon as the adaptation status " +
|
|
2736
|
+
"is 'ready_to_calibrate' or 'error'.")
|
|
2737
|
+
.withCategory("diagnostic"),
|
|
2738
|
+
e
|
|
2739
|
+
.enum("valve_adapt_process", ea.SET, ["adapt"])
|
|
2740
|
+
.withLabel("Trigger adaptation process")
|
|
2741
|
+
.withDescription("Trigger the valve adaptation process. Only possible when the adaptation status is 'ready_to_calibrate' or 'error'.")
|
|
2742
|
+
.withCategory("config"),
|
|
2743
|
+
];
|
|
2744
|
+
const fromZigbee = [
|
|
2745
|
+
{
|
|
2746
|
+
cluster: "hvacThermostat",
|
|
2747
|
+
type: ["attributeReport", "readResponse"],
|
|
2748
|
+
convert: async (model, msg, publish, options, meta) => {
|
|
2749
|
+
const result = {};
|
|
2750
|
+
const data = msg.data;
|
|
2751
|
+
if (data.valveAdaptStatus !== undefined) {
|
|
2752
|
+
result.valve_adapt_status = utils.getFromLookupByValue(data.valveAdaptStatus, valveAdaptStatusLookup);
|
|
2753
|
+
const automaticValveAdapt = meta.state.automatic_valve_adapt ?? false;
|
|
2754
|
+
if (automaticValveAdapt === true) {
|
|
2755
|
+
await triggerValveAdaptation(meta.state, msg.endpoint, false);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
if (data.automaticValveAdapt !== undefined) {
|
|
2759
|
+
result.automatic_valve_adapt = !!data.automaticValveAdapt;
|
|
2760
|
+
}
|
|
2761
|
+
return result;
|
|
2762
|
+
},
|
|
2763
|
+
},
|
|
2764
|
+
];
|
|
2765
|
+
const toZigbee = [
|
|
2766
|
+
{
|
|
2767
|
+
key: ["valve_adapt_status", "automatic_valve_adapt", "valve_adapt_process"],
|
|
2768
|
+
convertSet: async (entity, key, value, meta) => {
|
|
2769
|
+
if (key === "valve_adapt_process") {
|
|
2770
|
+
await triggerValveAdaptation(meta.state, entity);
|
|
2771
|
+
}
|
|
2772
|
+
},
|
|
2773
|
+
convertGet: async (entity, key, meta) => {
|
|
2774
|
+
if (key === "valve_adapt_status") {
|
|
2775
|
+
await entity.read("hvacThermostat", ["valveAdaptStatus"]);
|
|
2776
|
+
}
|
|
2777
|
+
if (key === "automatic_valve_adapt") {
|
|
2778
|
+
await entity.read("hvacThermostat", ["automaticValveAdapt"]);
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
},
|
|
2782
|
+
];
|
|
2783
|
+
const configure = [
|
|
2784
|
+
m.setupConfigureForReporting("hvacThermostat", "valveAdaptStatus", {
|
|
2785
|
+
config: { min: "MIN", max: "MAX", change: null },
|
|
2786
|
+
access: ea.STATE_GET,
|
|
2787
|
+
}),
|
|
2788
|
+
m.setupConfigureForReading("hvacThermostat", ["automaticValveAdapt"]),
|
|
2789
|
+
];
|
|
2790
|
+
return {
|
|
2791
|
+
exposes,
|
|
2792
|
+
fromZigbee,
|
|
2793
|
+
toZigbee,
|
|
2794
|
+
configure,
|
|
2795
|
+
isModernExtend: true,
|
|
2796
|
+
};
|
|
2797
|
+
},
|
|
2798
|
+
};
|
|
2068
2799
|
//endregion
|
|
2069
2800
|
//# sourceMappingURL=bosch.js.map
|