/* * TeslaChild * * Description: * This Hubitat driver provides a spot to put Tesla device data in children of the parent device. It has no * ability to check anything on it's own but the children make it much easier for dashboard and comparison purposes. * * Instructions for using Tile method: * 1) In "Preferences -> Tile Template" enter your template (example below) and click "Save Preferences" * Ex: "[b]Temperature:[/b] @temperature@°@location.getTemperatureScale()@[/br]" * 2) 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 * 3) Select the Add Tile button and the tile should appear * NOTE1: Put a @ before and after variable names * NOTE2: Should accept most HTML formatting commands with [] instead of <> * * Features List: * Ability to receive just state variables without posting events * Ability to clear state variables * Ability to check a website (mine) to notify user if there is a newer version of the driver available * * Licensing: * Copyright 2025 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: * 1.0.7 - General updates to event and state functions, replacement of Tile method, added additional attributes * 1.0.6 - Added additional attributes * 1.0.5 - Correction to remove Driver Status attribute * 1.0.4 - Correction to ProcessEvent function and removal of old driver-specific attributes when Preferences are saved * 1.0.3 - Corrected version information and update to ProcessEvent capability * 1.0.2 - Additional attributes, changes to newer method of update checking, and Tile Templates * 1.0.1 - Replaced all instances of boolean attributes with strings because boolean attributes are not valid * + changes to logging and update methods * 1.0.0 - "Release" version * 0.1.3 - Added additional capabilities/attributes * 0.1.2 - Added additional capabilities/attributes * 0.1.1 - Revised example for HTML Template in Preferences * 0.1.0 - Initial revision * * Thank you(s): * Thank you to @Cobra for inspiration of how I perform driver version checking * Thank you to @mircolino for working out a parent/child method and pointing out other areas for significant improvement as well as coming up with the * HTML Template method to assist in formatting children for dashboard use */ // Returns the driver name def DriverName(){ return "TeslaChild" } // Returns the driver version def DriverVersion(){ return "1.0.7" } // Driver Metadata metadata{ definition( name: "TeslaChild", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/TeslaChild.groovy" ) { capability "Sensor" capability "Battery" capability "PowerMeter" capability "PowerSource" capability "VoltageMeasurement" // Commands // Attributes // Driver identifies the driver being used for update purposes attribute "DriverName", "string" // Driver identifies the driver being used for update purposes attribute "DriverVersion", "string" // Version number of the driver attribute "DriverStatus", "string" // Status of the driver version compared to what is currently published // Attributes for the device data attribute "PowerwallStatus", "string" attribute "last_communication_time", "string" attribute "instant_power", "number" attribute "instant_reactive_power", "number" attribute "instant_apparent_power", "number" attribute "frequency", "number" attribute "energy_exported", "number" attribute "energy_imported", "number" attribute "instant_average_voltage", "number" attribute "instant_total_current", "number" attribute "i_a_current", "number" attribute "i_b_current", "number" attribute "i_c_current", "number" attribute "Grid Status", "string" attribute "Device Type", "string" attribute "Tesla Device Version", "string" attribute "System Energy (kWh)", "number" attribute "System Power (kW)", "number" attribute "SystemUp", "string" attribute "System Running", "string" attribute "Connected To Tesla", "string" attribute "Receiving Power From Grid", "string" attribute "Privacy Notice Signed", "string" attribute "Under Warranty", "string" attribute "Grid Connection Possible", "string" attribute "Receive Marketing", "string" attribute "System Registered", "string" attribute "Registration Period Over", "string" attribute "Instant Power", "number" attribute "Instant Reactive Power", "number" attribute "Instant Apparent Power", "number" attribute "Instant Total Current", "number" attribute "Instant Average Current", "number" attribute "Energy Exported", "number" attribute "Energy Imported", "number" attribute "Frequency (Hz)", "number" attribute "InstantAverageCurrent", "number" attribute "PINV_State", "string" // Tile Template attribute attribute "Tile", "string"; // Ex: "[b]Temperature:[/b] @temperature@°@location.getTemperatureScale()@[/br]" } preferences{ section{ if( ShowAllPreferences ){ input( name: "TileTemplate", type: "string", title: "Tile Template", description: "Ex: [b]Temperature:[/b] @temperature@°@location.getTemperatureScale()@[/br]", defaultValue: ""); input( type: "enum", name: "LogType", title: "Enable Logging?", required: true, 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(){ // Set the driver name and version before update checking is scheduled 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( "DriverStatus", null ) ProcessEvent( "DriverVersion", "${ DriverVersion() }" ) if( LogType == null ){ LogType = "Info" } unschedule() 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" ) } // 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 method to produce HTML formatted string for dashboard use private void UpdateTile( String val ){ if( TileTemplate != null ){ def TempString = "" Parsing = TileTemplate Parsing = Parsing.replaceAll( "\\[", "<" ) Parsing = Parsing.replaceAll( "\\]", ">" ) Count = Parsing.count( "@" ) if( Count >= 1 ){ def x = 1 while( x <= Count ){ TempName = Parsing.split( "@" )[ x ] switch( TempName ){ case "location.latitude": Value = location.latitude break case "location.longitude": Value = location.longitude break case "location.getTemperatureScale()": Value = location.getTemperatureScale() break default: Value = ReturnState( "${ TempName }" ) break } TempString = TempString + Parsing.split( "@" )[ ( x - 1 ) ] + Value x = ( x + 2 ) } if( Parsing.split( "@" ).last() != Parsing.split( "@" )[ Count - 1 ] ){ TempString = TempString + Parsing.split( "@" ).last() } } else if( Count == 1 ){ TempName = Parsing.split( "@" )[ 1 ] switch( TempName ){ case "location.latitude": Value = location.latitude break case "location.longitude": Value = location.longitude break case "location.getTemperatureScale()": Value = location.getTemperatureScale() break default: Value = ReturnState( "${ TempName }" ) break } TempString = TempString + Parsing.split( "@" )[ 0 ] + Value } else { TempString = TileTemplate } Logging( "Tile = ${ TempString }", 4 ) sendEvent( name: "Tile", value: TempString ) } } // Process data to check against current state value and then send an event if it has changed def ProcessEvent( Variable, Value, Unit = null, Description = null ){ sendEvent( name: Variable, value: Value, unit: Unit, descriptionText: Description ) Logging( "Event: ${ Variable } = ${ Value } Unit = ${ Unit } Description = ${ Description }", 4 ) ProcessState( Variable, Value ) UpdateTile( "${ Value }" ) } // Set a state variable to a value def ProcessState( 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() ) ProcessEvent( "DriverVersion", DriverVersion() ) 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 }" ) } else if( resp.data."${ DriverName() }".version == "REMOVED" ){ ProcessEvent( "DriverStatus", "Driver removed and no longer supported." ) } 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" ) } 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" ) } 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" ) } 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" ) } 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" ) } 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" ) } } } else { Logging( "${ DriverName() } is not published on drdsnell.com", 2 ) ProcessEvent( "DriverStatus", "${ DriverName() } is not published on drdsnell.com" ) } break default: Logging( "Unable to check drdsnell.com for ${ DriverName() } driver updates.", 2 ) break } } }