Skip to content

Commit 6fad358

Browse files
authored
Merge pull request #10 from fblackburn1/fix-power-outage
Pull request for fix-power-outage
2 parents 0923670 + 79956bb commit 6fad358

File tree

2 files changed

+136
-70
lines changed

2 files changed

+136
-70
lines changed

drivers/Plug-Sinope-SP2600ZB.groovy

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
/**
44
* Sinope Zigbee Plug (SP2600ZB) Device Driver for Hubitat
55
*
6-
* 1.0 (2022-12-18): initial release
6+
* 1.0 (2022-12-18): Initial release
7+
* 1.1 (2023-01-15): Set power to 0 when below treshold
78
* Author: fblackburn
89
* Inspired by:
910
* - Sinope => https://github.com/SmartThingsCommunity/SmartThingsPublic/tree/master/devicetypes/sinope-technologies
@@ -32,7 +33,10 @@ metadata
3233
name: 'powerChange',
3334
type: 'number',
3435
title: 'Power Change',
35-
description: 'Difference of watts to trigger power report (1..10000)',
36+
description: '''
37+
|Difference of watts to trigger power report (1..10000).
38+
|Also, power below this value will be considered as 0
39+
|'''.stripMargin(),
3640
range: '1..10000',
3741
defaultValue: getDefaultPowerChange()
3842
)
@@ -76,10 +80,6 @@ void configure() {
7680
unschedule()
7781
} catch (ignored) { }
7882

79-
if (device.currentValue('energy') == null) {
80-
sendEvent(name: 'energy', value: 0, unit: 'kWh')
81-
}
82-
8383
List cmds = []
8484
cmds += zigbee.configureReporting(0x0006, 0x0000, DataType.BOOLEAN, 0, 600, null) // State
8585
Integer powerChange = settings.powerChange == null ? getDefaultPowerChange() : settings.powerChange
@@ -105,8 +105,21 @@ Map parse(String description) {
105105
event.name = 'switch'
106106
event.value = getSwitchMap()[descMap.value]
107107
} else if (descMap.cluster == '0B04' && descMap.attrId == '050B') {
108+
Double power = getPower(descMap.value)
109+
Double oldPower = device.currentValue('power')
110+
if (power != 0.0 && power < oldPower) { // check if power decrease
111+
Integer powerChange = settings.powerChange == null ? getDefaultPowerChange() : settings.powerChange
112+
if (power < powerChange) {
113+
// No other even will be sent if power is lower than powerChange
114+
// So we will prioritze to send 0 value instead of the "real" value
115+
if (settings.trace) {
116+
log.trace "SP2600ZB >> power(${power}) hardcoded to 0"
117+
}
118+
power = 0.0
119+
}
120+
}
108121
event.name = 'power'
109-
event.value = getPower(descMap.value)
122+
event.value = power
110123
event.unit = 'W'
111124
} else if (descMap.cluster == '0702' && descMap.attrId == '0000') {
112125
BigInteger newEnergyValue = getEnergy(descMap.value)
@@ -121,7 +134,7 @@ Map parse(String description) {
121134
log.warn "SP2600ZB >> parse(descMap) ==> Unhandled attribute: ${descMap}"
122135
}
123136

124-
if (event.name && event.value) {
137+
if (event.name != null && event.value != null) {
125138
event.descriptionText = "${device.getLabel()} ${event.name} is ${event.value}"
126139
if (event.unit) {
127140
event.descriptionText = "${event.descriptionText}${event.unit}"

drivers/Thermostat-Sinope-TH1123ZB.groovy

Lines changed: 115 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
/**
44
* Thermostat Sinopé TH1123ZB-TH1124ZB Driver
55
*
6-
* 1.0 (2022-12-31): initial release
6+
* 1.0 (2022-12-31): Initial release
7+
* 1.1 (2022-01-04): Handled short circuit and rmsVoltage/rmsCurrent
78
* Author: fblackburn
89
* Inspired by:
910
* - Sinope => https://github.com/SmartThingsCommunity/SmartThingsPublic/tree/master/devicetypes/sinope-technologies
@@ -29,6 +30,8 @@ metadata
2930
capability 'Lock'
3031
capability 'PowerMeter'
3132
capability 'EnergyMeter'
33+
capability 'CurrentMeter'
34+
capability 'VoltageMeasurement'
3235

3336
attribute 'maxPower', 'number'
3437

@@ -159,89 +162,49 @@ void uninstalled() {
159162
unschedule()
160163
}
161164

162-
Map parse(String description) {
165+
List<Map> parse(String description) {
163166
if (!description?.startsWith('read attr -')) {
164167
if (!description?.startsWith('catchall:')) {
165168
log.warn "TH112XZB >> parse(description) ==> Unhandled event: ${description}"
166169
}
167-
return [:]
170+
return []
168171
}
169172

170-
Map event = [:]
171173
Map descMap = zigbee.parseDescriptionAsMap(description)
172-
if (descMap.cluster == '0201' && descMap.attrId == '0000') {
173-
String scale = getTemperatureScale()
174-
event.name = 'temperature'
175-
event.value = getTemperatureValue(descMap.value, scale)
176-
event.unit = "°${scale}"
177-
} else if (descMap.cluster == '0201' && descMap.attrId == '0008') {
178-
Integer heatingDemand = getHeatingDemand(descMap.value)
179-
event.name = 'heatingDemand'
180-
event.value = heatingDemand
181-
event.unit = '%'
182-
174+
Map event = extractEvent(descMap)
175+
List<Map> events = [event]
176+
if (event.name == 'heatingDemand') {
183177
String operatingState = (event.value.toInteger() < 10) ? 'idle' : 'heating'
184178
Map opEvent = ['name': 'thermostatOperatingState', 'value': operatingState]
185179
opEvent.descriptionText = generateDescription(opEvent)
186180
if (settings.trace) {
187181
log.trace "TH112XZB >> parse(description)[generated] ==> ${opEvent.name}: ${opEvent.value}"
188182
}
189-
sendEvent(opEvent)
183+
events.add(opEvent)
190184

191185
Integer maxPower = device.currentValue('maxPower')
192186
if (maxPower != null) {
193-
Integer power = Math.round(maxPower * heatingDemand / 100)
187+
Integer power = Math.round(maxPower * event.value / 100)
194188
Map powerEvent = [name: 'power', value: power, unit: 'W']
195189
powerEvent.descriptionText = generateDescription(powerEvent)
196190
if (settings.trace) {
197191
log.trace "TH112XZB >> parse(description)[generated] ==> ${powerEvent.name}: ${powerEvent.value}"
198192
}
199-
sendEvent(powerEvent)
200-
}
201-
} else if (descMap.cluster == '0702' && descMap.attrId == '0000') {
202-
BigInteger energy = getEnergy(descMap.value)
203-
if (energy == 0) {
204-
// FIXME Not able to reproduce this behavior with TH112XZB
205-
log.warn 'TH112XZB >> Ignoring energy event (Caused: unknown)'
206-
} else {
207-
event.name = 'energy'
208-
event.value = energy / 1000
209-
event.unit = 'kWh'
193+
events.add(powerEvent)
210194
}
211-
} else if (descMap.cluster == '0B04' && descMap.attrId == '050B') {
212-
event.name = 'power'
213-
event.value = getPower(descMap.value)
214-
event.unit = 'W'
215-
} else if (descMap.cluster == '0B04' && descMap.attrId == '050D') {
216-
event.name = 'maxPower'
217-
event.value = getPower(descMap.value)
218-
event.unit = 'W'
219-
} else if (descMap.cluster == '0201' && descMap.attrId == '0012') {
220-
String scale = getTemperatureScale()
221-
event.name = 'heatingSetpoint'
222-
event.value = getTemperatureValue(descMap.value, scale, true)
223-
event.unit = "°${scale}"
224-
} else if (descMap.cluster == '0201' && descMap.attrId == '0014') {
225-
String scale = getTemperatureScale()
226-
event.name = 'heatingSetpoint'
227-
event.value = getTemperatureValue(descMap.value, scale, true)
228-
event.unit = "°${scale}"
229-
} else if (descMap.cluster == '0201' && descMap.attrId == '001C') {
230-
event.name = 'thermostatMode'
231-
event.value = getModeMap()[descMap.value]
232-
} else if (descMap.cluster == '0204' && descMap.attrId == '0001') {
233-
event.name = 'lock'
234-
event.value = getLockMap()[descMap.value]
235-
} else {
236-
log.warn "TH112XZB >> parse(descMap) ==> Unhandled attribute: ${descMap}"
237-
return [:]
238195
}
239-
event.descriptionText = generateDescription(event)
240-
241-
if (settings.trace) {
242-
log.trace "TH112XZB >> parse(description) ==> ${event.name}: ${event.value}"
196+
if (descMap.additionalAttrs) {
197+
// When many events from same cluster must be sent at the same time,
198+
// device other events in additionalAttrs instead of sending several
199+
if (settings.trace) {
200+
log.trace "TH112XZB >> Found additionalAttrs: ${descMap}"
201+
}
202+
descMap.additionalAttrs.each { Map attribute ->
203+
attribute.cluster = descMap.cluster
204+
events.add(extractEvent(attribute))
205+
}
243206
}
244-
return event
207+
return events
245208
}
246209

247210
void unlock() {
@@ -286,6 +249,8 @@ void refresh() {
286249
cmds += zigbee.readAttribute(0x0201, 0x0008) // PI heating demand
287250
cmds += zigbee.readAttribute(0x0201, 0x001C) // System Mode
288251
cmds += zigbee.readAttribute(0x0204, 0x0001) // Keypad lock
252+
cmds += zigbee.readAttribute(0x0B04, 0x0505) // RMS Voltage
253+
cmds += zigbee.readAttribute(0x0B04, 0x0508) // RMS Current
289254
cmds += zigbee.readAttribute(0x0B04, 0x050B) // Active power
290255
cmds += zigbee.readAttribute(0x0B04, 0x050D) // Maximum power available
291256
cmds += zigbee.readAttribute(0x0702, 0x0000) // Total Energy
@@ -336,7 +301,6 @@ void setClockTime() {
336301
log.trace 'TH112XZB >> setClockTime()'
337302
}
338303

339-
340304
/* groovylint-disable-next-line NoJavaUtilDate */
341305
Date now = new Date()
342306
Long currentTimeSec = now.getTime() / 1000
@@ -440,9 +404,84 @@ void handlePowerOutage() {
440404
setClockTime()
441405
}
442406

407+
private Map extractEvent(Map descMap) {
408+
Map event = [:]
409+
if (descMap.cluster == '0201' && descMap.attrId == '0000') {
410+
String scale = getTemperatureScale()
411+
event.name = 'temperature'
412+
event.value = getTemperatureValue(descMap.value, scale)
413+
event.unit = "°${scale}"
414+
} else if (descMap.cluster == '0201' && descMap.attrId == '0008') {
415+
event.name = 'heatingDemand'
416+
event.value = getHeatingDemand(descMap.value)
417+
event.unit = '%'
418+
} else if (descMap.cluster == '0702' && descMap.attrId == '0000') {
419+
BigInteger energy = getEnergy(descMap.value)
420+
Double previousEnergy = device.currentValue('energy')
421+
if (energy < previousEnergy) {
422+
// When a baseboard heater is too hot, a short circuit is created for few seconds until the unit cools down.
423+
// This kind of power outage, reset to an old "random" value
424+
// Note: For some unknown reason, power outage from electrical board doesn't reset value ...
425+
// If you have this warning you should verify that nothing prevents the release of heat from your heater
426+
// (ex: curtains, bedding, reverse installation, etc)
427+
/* groovylint-disable-next-line LineLength */
428+
log.warn "TH112XZB >> Energy[${energy}] is lower than previous one[${previousEnergy}] (Caused: short circuit from heater)"
429+
}
430+
event.name = 'energy'
431+
event.value = energy / 1000
432+
event.unit = 'kWh'
433+
} else if (descMap.cluster == '0B04' && descMap.attrId == '050B') {
434+
event.name = 'power'
435+
event.value = getPower(descMap.value)
436+
event.unit = 'W'
437+
} else if (descMap.cluster == '0B04' && descMap.attrId == '050D') {
438+
event.name = 'maxPower'
439+
event.value = getPower(descMap.value)
440+
event.unit = 'W'
441+
} else if (descMap.cluster == '0201' && descMap.attrId == '0012') {
442+
String scale = getTemperatureScale()
443+
event.name = 'heatingSetpoint'
444+
event.value = getTemperatureValue(descMap.value, scale, true)
445+
event.unit = "°${scale}"
446+
} else if (descMap.cluster == '0201' && descMap.attrId == '0014') {
447+
String scale = getTemperatureScale()
448+
event.name = 'heatingSetpoint'
449+
event.value = getTemperatureValue(descMap.value, scale, true)
450+
event.unit = "°${scale}"
451+
} else if (descMap.cluster == '0201' && descMap.attrId == '001C') {
452+
event.name = 'thermostatMode'
453+
event.value = getModeMap()[descMap.value]
454+
} else if (descMap.cluster == '0204' && descMap.attrId == '0001') {
455+
event.name = 'lock'
456+
event.value = getLockMap()[descMap.value]
457+
} else if (descMap.cluster == '0B04' && descMap.attrId == '0505') {
458+
// This event seems to be triggered automatically after each 18 hours
459+
event.name = 'voltage'
460+
event.value = getVoltage(descMap.value)
461+
event.unit = 'V'
462+
} else if (descMap.cluster == '0B04' && descMap.attrId == '0508') {
463+
event.name = 'amperage'
464+
event.value = getAmperage(descMap.value)
465+
event.unit = 'A'
466+
} else if (descMap.cluster == '0B04' && descMap.attrId == '0551') {
467+
BigInteger energy = getEnergy(descMap.value)
468+
log.trace "TH112XZB >> Skipping duplicate event[0551] energy': ${energy}"
469+
return [:]
470+
} else {
471+
log.warn "TH112XZB >> parse(descMap) ==> Unhandled attribute: ${descMap}"
472+
return [:]
473+
}
474+
event.descriptionText = generateDescription(event)
475+
476+
if (settings.trace) {
477+
log.trace "TH112XZB >> parse(description) ==> ${event.name}: ${event.value}"
478+
}
479+
return event
480+
}
481+
443482
private String generateDescription(Map event) {
444483
String description = null
445-
if (event.name && event.value) {
484+
if (event.name != null && event.value != null) {
446485
description = "${device.getLabel()} ${event.name} is ${event.value}"
447486
if (event.unit) {
448487
description = "${description}${event.unit}"
@@ -561,6 +600,20 @@ private BigInteger getEnergy(String value) {
561600
return new BigInteger(value, 16)
562601
}
563602

603+
private Double getVoltage(String value) {
604+
if (value == null) {
605+
return 0
606+
}
607+
return Integer.parseInt(value, 16) / 10
608+
}
609+
610+
private Double getAmperage(String value) {
611+
if (value == null) {
612+
return 0
613+
}
614+
return Integer.parseInt(value, 16) / 1000
615+
}
616+
564617
private Map getModeMap() {
565618
return [
566619
'00': 'off',

0 commit comments

Comments
 (0)