/*
* UnifiConnectChild-UC-Display-13
*
* Description:
* This Hubitat driver provides a spot to put data from Unifi Connect Display 13 devices. It does not belong on it's own and requires
* the UnifiConnectAPI driver as a parent device.
*
* Instructions for using Tile Template method (originally based on @mircolino's HTML Templates):
* 1) In "Hubitat -> Devices" select the child/sensor (not the parent) you would like to "templetize"
* 2) In "Preferences -> Tile Template" enter your template (example below) and click "Save Preferences"
* Ex: "[font size='2'][b]Temperature:[/b] ${ temperature }°${ location.getTemperatureScale() }[/br][/font]"
* 3) In a Hubitat dashboard, add a new tile, and select the child/sensor, in the center select "Attribute", and on the right select the "Tile" attribute
* 4) Select the Add Tile button and the tile should appear
* NOTE: Should accept most HTML formatting commands with [] instead of <>
*
* Features List:
* Ability to turn on/off the display
* Ability to set the volume of the display
* Ability to set the brightness of the display
* Ability to start/stop locating light
* Ability to reboot the display
* Ability to check a website (mine) to notify user if there is a newer version of the driver available
*
* Licensing:
* Copyright 2024 David Snell
* Licensed under 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.
*
* Version Control:
* 0.1.2 - Renaming of driver-specific attributes and correction to ProcessEvent
* 0.1.1 - Overhaul of how actions are handled
* 0.1.0 - Initial version
*
* Thank you(s):
* @Cobra for inspiration of how I perform driver version checking
* @mircolino for HTML Template method for dashboard use
*/
// Returns the driver name
def DriverName(){
return "UnifiConnectChild-UC-Display-13"
}
// Returns the driver version
def DriverVersion(){
return "0.1.2"
}
// Driver Metadata
metadata{
definition( name: "UnifiConnectChild-UC-Display-13", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/UnifiConnectChild-UC-Display-13.groovy" ) {
capability "Sensor"
capability "Actuator"
capability "Switch"
// Commands
//command "DoSomething" // For testing and development purposes only, it should not be uncommented for normal use
command "play" //
command "stop" //
//command "display_switch" //
command "start_locating" //
command "stop_locating" //
command "reboot" //
command "set_brightness", [ [ name: "Brightness", type: "NUMBER", range: "0..200" ] ] //
command "enable_auto_rotate" //
command "set_volume", [ [ name: "Volume", type: "NUMBER", range: "0..40" ] ] //
//command "fw_update" //
command "enable_sleep" //
command "disable_sleep" //
//command "enable_memorize_playlist" //
//command "disable_memorize_playlist" //
command "disable_auto_rotate" //
command "rotate", [ [ name: "Rotation", type: "ENUM", constraints: [ "Portrait", "Landscape", "Portrait (flipped)", "Landscape (flipped)" ] ] ] //
//command "load_website" //
//command "refresh_website" //
command "enable_auto_reload" //
command "disable_auto_reload" //
//command "upgrade_mode" //
//command "launch_app" //
//command "stop_app" //
// Attributes for the driver itself
attribute "DriverName", "string" // Identifies the driver being used for update purposes
attribute "DriverVersion", "string" // Handles version for driver
attribute "DriverStatus", "string" // Handles version notices for driver
// Attributes - Device Related
attribute "Status", "string" // Show success/failure of commands performed
attribute "Device Status", "string" // Show the current state of the device as reported by the controller
attribute "Device Type", "string" // What type of device the child is respresenting
// General Device Attributes
attribute "Type", "string" // The type of device, per API
attribute "ID", "string" // ID of the device, per API
attribute "Mode", "string" //
attribute "Volume", "number" // Max volume is 40?
attribute "Brightness", "number" // Max brightness is 255
attribute "switch", "enum", [ "on", "off" ]
attribute "Rotation", "enum", [ "Portrait", "Landscape", "Portrait (flipped)", "Landscape (flipped)" ] //
// Tile Template attribute
attribute "Tile", "string"; // Ex: "[font size='2'][b]Temperature:[/b] ${ temperature }°${ location.getTemperatureScale() }[/br][/font]"
}
preferences{
//section{
if( ShowAllPreferences ){
//input( type: "string", name: "DeviceName", title: "Device Name", description: "If set it will change the device's name on the controller.", defaultValue: "${ device.label }")
input( name: "TileTemplate", type: "string", title: "Tile Template", description: "Ex: [b]Temperature:[/b] \${ state.temperature }°${ location.getTemperatureScale() }[/br]", defaultValue: "");
input( type: "enum", name: "LogType", title: "Enable Logging?", required: false, multiple: false, options: [ "None", "Info", "Debug", "Trace" ], defaultValue: "Info" )
input( type: "bool", name: "ShowAllPreferences", title: "Show All Preferences?", defaultValue: true )
} else {
input( type: "bool", name: "ShowAllPreferences", title: "Show All Preferences?", defaultValue: true )
}
//}
}
}
// updated
def updated(){
if( LogType == null ){
LogType = "Info"
}
if( state."Driver Status" != null ){
state.remove( "Driver Name" )
state.remove( "Driver Version" )
state.remove( "Driver Status" )
device.deleteCurrentState( "Driver Status" )
device.deleteCurrentState( "Driver Name" )
device.deleteCurrentState( "Driver Version" )
}
ProcessEvent( "DriverName", "${ DriverName() }" )
ProcessEvent( "DriverVersion", "${ DriverVersion() }" )
ProcessEvent( "DriverStatus", null )
// Schedule daily check for driver updates to notify user
def Hour = ( new Date().format( "h" ) as int )
def Minute = ( new Date().format( "m" ) as int )
def Second = ( new Date().format( "s" ) as int )
// Schedule checks that are only performed once a day
schedule( "${ Second } ${ Minute } ${ Hour } ? * *", "CheckForUpdate" )
Logging( "Updated", 2 )
}
// DoSomething is for testing and development purposes. It should not be uncommented for normal usage.
def DoSomething(){
}
// Send a command to turn on the display
def on(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "d812bb7b-6a83-46d2-82d4-4012b9e1b6b4", "display_on" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot turn on", 5 )
}
}
// Send a command to turn off the display
def off(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "54dc5c8f-7a97-4cba-8146-d1f596209423", "display_off" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot turn off", 5 )
}
}
// Send a command to set the display to start playing
def play(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "99b4079f-a4c1-44f5-b467-4105166e6a09", "play" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot play", 5 )
}
}
// Send a command to set the display to stop
def stop(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "d2a51ad9-a18c-4fa2-aef1-5baa1b0513ca", "stop" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot stop", 5 )
}
}
// Send a command to set the display to switch
def display_switch(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "6bce5e0b-bcdd-4f8e-842a-f873926b224d", "switch" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot switch", 5 )
}
}
// Send a command to set the display to start locating
def start_locating(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "9323c4d7-2b0b-4cb2-a9e6-aa5297b40962", "start_locating" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot start locating", 5 )
}
}
// Send a command to set the display to stop locating
def stop_locating(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "22c55a96-9124-40d9-b15b-51bc418c60e2", "stop_locating" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot stop locating", 5 )
}
}
// Send a command to set the display to reboot
def reboot(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "9a25b0e3-de8b-4798-86c9-91120af63075", "reboot" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot reboot", 5 )
}
}
// Send a command to set the display to set brightness
def set_brightness( Value ){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "e3452521-8ed6-45d2-9057-4a57a7d3c533", "brightness", "\"value\":${ Value }" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set brightness", 5 )
}
}
// Send a command to set the display to enable auto rotate
def enable_auto_rotate(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "b527f641-dabf-4987-8423-72f9eebd8373", "enable_auto_rotate" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set enable auto rotate", 5 )
}
}
// Send a command to set the display to set volume
def set_volume( Value ){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "7d68c3d0-8321-496a-88df-da0f81e195f6", "volume", "\"value\":${ Value }" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set volume", 5 )
}
}
// Send a command to set the display to firmware update
def fw_update(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "7b0f4669-46e7-48fc-a705-e5b433566f73", "fw update" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot firmware update", 5 )
}
}
// Send a command to set the display to enable sleep
def enable_sleep(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "79c46140-efe6-46b9-acd6-e8c06320e828", "enable_sleep" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot enable sleep", 5 )
}
}
// Send a command to set the display to disable sleep
def disable_sleep(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "68858d78-b575-4d6d-af1c-2ba1c853948f", "disable_sleep" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot disable sleep", 5 )
}
}
// Send a command to set the display to enable memorize playlist
def enable_memorize_playlist(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "6f7b9b6d-9c20-4b34-90d5-59ec11b2b268", "enable_memorize_playlist" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot enable memorize playlist", 5 )
}
}
// Send a command to set the display to disable memorize playlist
def disable_memorize_playlist(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "f7cd57e9-d36e-4e13-8620-28dc093493c1", "disable_memorize_playlist" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot disable memorize playlist", 5 )
}
}
// Send a command to set the display to disable auto rotate
def disable_auto_rotate(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "6f03bd60-651f-4049-880e-85a956a67928", "disable_auto_rotate" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot disable auto rotate", 5 )
}
}
// Send a command to set the display to rotate
def rotate( Value ){
if( state.ID != null ){
def Args
switch( Value ){
case "Portrait":
Args = "\"scale\":\"portraitPrim\""
break
case "Landscape":
Args = "\"scale\":\"landscapePrim\""
break
case "Portrait (flipped)":
Args = "\"scale\":\"portraitSec\""
break
case "Landscape (flipped)":
Args = "\"scale\":\"landscapeSec\""
break
}
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "30821f50-9e3b-48be-869e-3733734d148e", "rotate", Args )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot rotate", 5 )
}
}
// Send a command to set the display to load website
def load_website(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "c2fcb688-cf88-4516-8fad-b687c3fce664", "load_website" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot load website", 5 )
}
}
// Send a command to set the display to refresh website
def refresh_website(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "90edc287-e141-4991-9a6b-d7a821b681f5", "refresh_website" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot refresh website", 5 )
}
}
// Send a command to set the display to enable auto reload
def enable_auto_reload(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "19c019a8-6aa0-42ad-aed7-d17da0a720fb", "enable_auto_reload" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot enable auto reload", 5 )
}
}
// Send a command to set the display to disable auto reload
def disable_auto_reload(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "1e9e8974-f020-423e-b11a-5b5e1180d41a", "disable_auto_reload" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot disable auto reload", 5 )
}
}
// Send a command to set the display to upgrade mode
def upgrade_mode(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "78e3130a-d28f-45fb-af6c-35637743e160", "upgrade_mode" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot upgrade mode", 5 )
}
}
// Send a command to set the display to launch app
def launch_app(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "11a9c80e-31a3-4d28-9847-63658e87c447", "launch_app" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot launch app", 5 )
}
}
// Send a command to set the display to stop app
def stop_app(){
if( state.ID != null ){
parent.PerformAction( device.getDeviceNetworkId(), state.ID, "06ca2905-4d9d-42ab-a8d0-5aea03a927f4", "stop_app" )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot stop app", 5 )
}
}
// installed is called when the device is installed, all it really does is run updated
def installed(){
Logging( "Installed", 2 )
updated()
}
// initialize is called when the device is initialized, all it really does is run updated
def initialize(){
Logging( "Initialized", 2 )
updated()
}
// Return a state value
def ReturnState( Variable ){
return state."${ Variable }"
}
// Tile Template method based on @mircolino's HTML Template method
private void UpdateTile( String val ){
if( settings.TileTemplate ){
// Create special compound/html tile
val = settings.TileTemplate.toString().replaceAll( "\\[", "<" )
val = val.replaceAll( "\\]", ">" )
val = val.replaceAll( ~/\$\{\s*([A-Za-z][A-Za-z0-9_]*)\s*\}/ ) { java.util.ArrayList m -> device.currentValue("${ m [ 1 ] }").toString() }
if( device.currentValue( "Tile" ).toString() != val ){
sendEvent( name: "Tile", value: val )
}
}
}
// Process data to check against current state value and then send an event if it has changed
def ProcessEvent( Variable, Value, Unit = null, ForceEvent = false ){
if( ( state."${ Variable }" != Value ) || ( ForceEvent == true ) ){
state."${ Variable }" = Value
if( Unit != null ){
Logging( "Event: ${ Variable } = ${ Value }${ Unit }", 4 )
sendEvent( name: "${ Variable }", value: Value, unit: Unit, isStateChange: true )
} else {
Logging( "Event: ${ Variable } = ${ Value }", 4 )
sendEvent( name: "${ Variable }", value: Value, isStateChange: true )
}
UpdateTile( "${ Value }" )
}
}
// Process data to check against current state value
def ProcessState( Variable, Value ){
if( state."${ Variable }" != Value ){
Logging( "State: ${ Variable } = ${ Value }", 4 )
state."${ Variable }" = Value
UpdateTile( "${ Value }" )
}
}
// Handles whether logging is enabled and thus what to put there.
def Logging( LogMessage, LogLevel ){
// Add all messages as info logging
if( ( LogLevel == 2 ) && ( LogType != "None" ) ){
log.info( "${ device.displayName } - ${ LogMessage }" )
} else if( ( LogLevel == 3 ) && ( ( LogType == "Debug" ) || ( LogType == "Trace" ) ) ){
log.debug( "${ device.displayName } - ${ LogMessage }" )
} else if( ( LogLevel == 4 ) && ( LogType == "Trace" ) ){
log.trace( "${ device.displayName } - ${ LogMessage }" )
} else if( LogLevel == 5 ){
log.error( "${ device.displayName } - ${ LogMessage }" )
}
}
// Checks drdsnell.com for the latest version of the driver
// Original inspiration from @cobra's version checking
def CheckForUpdate(){
ProcessEvent( "DriverName", DriverName(), null, true )
ProcessEvent( "DriverVersion", DriverVersion(), null, true )
ProcessEvent( "DriverStatus", null, null, true )
httpGet( uri: "https://www.drdsnell.com/projects/hubitat/drivers/versions.json", contentType: "application/json" ){ resp ->
switch( resp.status ){
case 200:
if( resp.data."${ DriverName() }" ){
CurrentVersion = DriverVersion().split( /\./ )
if( resp.data."${ DriverName() }".version == "REPLACED" ){
ProcessEvent( "DriverStatus", "Driver replaced, please use ${ resp.data."${ state.DriverName }".file }", null, true )
} else if( resp.data."${ DriverName() }".version == "REMOVED" ){
ProcessEvent( "DriverStatus", "Driver removed and no longer supported.", null, true )
} else {
SiteVersion = resp.data."${ DriverName() }".version.split( /\./ )
if( CurrentVersion == SiteVersion ){
Logging( "Driver version up to date", 3 )
ProcessEvent( "DriverStatus", "Up to date" )
} else if( ( CurrentVersion[ 0 ] as int ) > ( SiteVersion [ 0 ] as int ) ){
Logging( "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
ProcessEvent( "DriverStatus", "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", null, true )
} else if( ( CurrentVersion[ 1 ] as int ) > ( SiteVersion [ 1 ] as int ) ){
Logging( "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
ProcessEvent( "DriverStatus", "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", null, true )
} else if( ( CurrentVersion[ 2 ] as int ) > ( SiteVersion [ 2 ] as int ) ){
Logging( "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
ProcessEvent( "DriverStatus", "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", null, true )
} else if( ( SiteVersion[ 0 ] as int ) > ( CurrentVersion[ 0 ] as int ) ){
Logging( "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
ProcessEvent( "DriverStatus", "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", null, true )
} else if( ( SiteVersion[ 1 ] as int ) > ( CurrentVersion[ 1 ] as int ) ){
Logging( "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
ProcessEvent( "DriverStatus", "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", null, true )
} else if( ( SiteVersion[ 2 ] as int ) > ( CurrentVersion[ 2 ] as int ) ){
Logging( "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
ProcessEvent( "DriverStatus", "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", null, true )
}
}
} else {
Logging( "${ DriverName() } is not published on drdsnell.com", 2 )
ProcessEvent( "DriverStatus", "${ DriverName() } is not published on drdsnell.com", null, true )
}
break
default:
Logging( "Unable to check drdsnell.com for ${ DriverName() } driver updates.", 2 )
break
}
}
}