/* groovylint-disable CouldBeSwitchStatement, DuplicateNumberLiteral, MethodCount, MethodParameterTypeRequired, NoDef, StaticMethodsBeforeInstanceMethods, UnnecessaryElseStatement, UnnecessaryGetter, UnnecessarySetter, VariableTypeRequired */
/*
* ''Matter Generic Component Window Shade' - component driver for Matter Advanced Bridge
*
* https://community.hubitat.com/t/dynamic-capabilities-commands-and-attributes-for-drivers/98342
* https://community.hubitat.com/t/project-zemismart-m1-matter-bridge-for-tuya-zigbee-devices-matter/127009
*
* Licensed Virtual the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* This library is inspired by @w35l3y work on Tuya device driver (Edge project).
* For a big portions of code all credits go to Jonathan Bradshaw.
*
*
* ver. 1.0.0 2024-03-16 kkossev - first release
* ver. 1.1.0 2025-01-12 kkossev - (dev.branch) added capabilities 'Switch' and 'SwitchLevel'
*
* TODO: bugfix: Curtain driver exception @UncleAlias #4
*/
import groovy.transform.Field
@Field static final String matterComponentWindowShadeVersion = '1.1.0'
@Field static final String matterComponentWindowShadeStamp = '2025/01/12 8:39 PM'
@Field static final Boolean _DEBUG = false
@Field static final Integer OPEN = 0 // this is the standard! Hubitat is inverted?
@Field static final Integer CLOSED = 100 // this is the standard! Hubitat is inverted?
@Field static final Integer POSITION_DELTA = 5
@Field static final Integer MAX_TRAVEL_TIME = 15
@Field static final Boolean SIMULATE_LEVEL = true
metadata {
definition(name: 'Matter Generic Component Window Shade', namespace: 'kkossev', author: 'Krassimir Kossev', importUrl: 'https://raw.githubusercontent.com/kkossev/Hubitat---Matter-Advanced-Bridge/main/Components/Matter_Generic_Component_Window_Shade.groovy', singleThreaded: true) {
capability 'Actuator'
capability 'WindowShade' // Attributes: position - NUMBER, unit:% windowShade - ENUM ["opening", "partially open", "closed", "open", "closing", "unknown"]
// Commands: close(); open(); setPosition(position) position required (NUMBER) - Shade position (0 to 100);
// startPositionChange(direction): direction required (ENUM) - Direction for position change request ["open", "close"]
// stopPositionChange()
capability 'Refresh'
capability 'Battery'
capability 'Switch'
capability 'SwitchLevel'
//capability 'Health Check' // Commands:[ping]
//attribute 'healthStatus', 'enum', ['unknown', 'offline', 'online']
attribute 'targetPosition', 'number' // ZemiSmart M1 is updating this attribute, not the position :(
attribute 'operationalStatus', 'number' // 'enum', ['unknown', 'open', 'closed', 'opening', 'closing', 'partially open']
attribute 'batteryVoltage', 'number'
attribute 'batStatus', 'string' // Aqara E1 blinds
attribute 'batOrder', 'string' // Aqara E1 blinds
attribute 'batDescription', 'string' // Aqara E1 blinds
attribute 'batTimeRemaining', 'string'
attribute 'batChargeLevel', 'string' // Aqara E1 blinds
attribute 'batReplacementNeeded', 'string' // Aqara E1 blinds
attribute 'batReplaceability', 'string'
attribute 'batReplacementDescription', 'string'
attribute 'batQuantity', 'string'
command 'initialize', [[name: 'initialize all attributes']]
if (_DEBUG) {
command 'parseTest', [[name: 'parseTest', type: 'STRING', description: 'parseTest', defaultValue : '']]
}
}
}
preferences {
section {
input name: "helpInfo", type: "hidden", title: fmtHelpInfo("Community Link")
input name: 'txtEnable', type: 'bool', title: 'Enable descriptionText logging', required: false, defaultValue: true
input name: 'logEnable', type: 'bool', title: 'Enable debug logging', required: false, defaultValue: false
input name: 'maxTravelTime', type: 'number', title: 'Maximum travel time', description: 'The maximum time to fully open or close (Seconds)', required: false, defaultValue: MAX_TRAVEL_TIME
input name: 'deltaPosition', type: 'number', title: 'Position delta', description: 'The maximum error step reaching the target position', required: false, defaultValue: POSITION_DELTA
input name: 'substituteOpenClose', type: 'bool', title: 'Substitute Open/Close w/ setPosition', description: 'Non-standard Zemismart motors', required: false, defaultValue: false
input name: 'invertPosition', type: 'bool', title: 'Reverse Position Reports', description: 'Non-standard Zemismart motors', required: false, defaultValue: false
input name: 'targetAsCurrentPosition', type: 'bool', title: 'Reverse Target and Current Position', description: 'Non-standard Zemismart motors', required: false, defaultValue: false
}
}
int getDelta() { return settings?.deltaPosition != null ? settings?.deltaPosition as int : POSITION_DELTA }
//int getFullyOpen() { return settings?.invertOpenClose ? CLOSED : OPEN }
//int getFullyClosed() { return settings?.invertOpenClose ? OPEN : CLOSED }
//int getFullyOpen() { return settings?.invertPosition ? CLOSED : OPEN }
//int getFullyClosed() { return settings?.invertPosition ? OPEN : CLOSED }
int getFullyOpen() { return OPEN }
int getFullyClosed() { return CLOSED }
boolean isFullyOpen(int position) { return Math.abs(position - getFullyOpen()) < getDelta() }
boolean isFullyClosed(int position) { return Math.abs(position - getFullyClosed()) < getDelta() }
// parse commands from parent
void parse(List