/* * Neptune Systems Apex * * Description: * This Hubitat driver polls the status.json page provided by a Neptune Systems Apex (Classic or 2016) and * then reads the current values of selected sensors so that they can be used by Hubitat Rules or other * purposes. It also has a check for newer versions of the driver itself. As of version 0.93 it can also read * ALL inputs and outputs listed in the json file. However, these are added in the events list and on the device * page this can get quite long because they do not have individual attributes due to the variability of names. * * Initial Setup: * 1) Load the parent driver (NeptuneSystemsApex.groovy) and the OPTIONAL desired child driver(s) onto the Hubitat. * a) Go into the drivers section on your Hubitat * b) Select the New Driver button * c) Select the Import button * d) Copy/paste the NeptuneSystemsApex.groovy link from the very first post into the field and select Import. * e) Select the Save button. * f) Repeat a-e for the child drivers if you will want to enable child devices. * NOTE: Load the Hubitat NeptuneSystemsApexChild.groovy if you want the read-only generic child driver * Load the NSChild-.groovy drivers if you want active control capabilities * WARNING: Actively controlling your Neptune System modules from Hubitat CAN interfere with their control by the Apex itself as it * is replicating the manual override methods a user can select in their browser interface. * 3) Go to the Devices section on your Hubitat. * 4) Select the Add Device button. * 5) Select the Virtual button. * 6) Enter the device name you want, select NeptuneSystemsApex from the Type dropdown, then select the Save Device button. * 7) In the parent device preferences, enter the Apex controller's URL. If you have authentication required and/or going to perform * active control, you MUST also enter your username/password as part of the URL. * It would be formatted like: http://[username]:[password]@[IP/Hostname] * Just replace [username] with your Apex's admin username, [password] with the appropriate password and * [IP/Hostname] with the IP or Hostname used to get to the Apex (as normal now). * Identified devices: * Controllers it SHOULD identify include: * ApexClassic * ApexJr * ApexEL * Apex2016 * ApexA3Pro * ApexA3 * Controller(s) it MAY identify include (if you have one, please send me a sample status.json): * ApexA3Jr * Controllers it DOES NOT YET identify (if you have one, please send me a sample status.json): * None known at this time * Modules it SHOULD identify include: * ASM, base, EB4, EB8, EB832, PM1 (if pH in use), PM2 (if Cond in use), AFS, FMM, VDM, AI, Vortech, DOS, Trident, * SKY, COR, Sicce (Pumps), WAV, & MXM * Modules it MAY identify (if you have one, please send me a sample status.json for confirmation): * PM3 (if DO in use) * Modules it DOES NOT YET identify (if you have one, please send me a sample status.json): * LSM and WXM * Modules it CANNOT identify: * ATK, FMK, or LDK because those kits actually use the FMM module (which should be identified) * PMK because that kit actually uses the ASM module (which SHOULD be identified per above) * * Features List: * Child device module identification (if Children are enabled) * Child devices for each module in status.json (if Children are enabled) * Added 4 Apex-specific attributes for outputs (2 number & 2 strings) * Added 4 Apex-specific number attributes for inputs * Option to show/hide all preference settings * Option to send events for all outputs * Option to send events for all inputs * State displaying the software version of the Apex * Ability to check a website (mine) to notify user if there is a newer version of the driver available * Ability to specify a polling interval for the sensors * Ability to read/create an event for a single pH (with controllable variable name) * Ability to read/create an event for a single ORP (with controllable variable name) * Ability to read/create an event for a single Salinity (with controllable variable name) * Ability to read/create an event for a single Flow Rate (with controllable variable name) * Ability to set the URL of the Apex to be queried * Ability to read/create an event for a single temperature (with controllable variable name) * * 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.100.19 - Correction to ProcessEvent function and removal of old driver-specific attributes when Preferences are saved * 0.100.18 - Attempt at recognition of flow data reported for EB832 and FMM modules (including those integrated into Apex A3) * 0.100.17 - Additional error checking around input and output data as well as logging of response received * 0.100.16 - Apex EL and ASM recognition changes * 0.100.15 - Initial work to support MXM Module, including Radion lighting, Vectra & Vortech pumps * 0.100.14 - Minor cleanup in module processing code * 0.100.13 - Minor change to allow ProcessEvent to "force" an Event changed notice, changed GetCookie interval to every hour, * added data type "alert" being returned for some modules besides base, and perform refresh when active control returns "AUTO" * 0.100.12 - Correction for module type check being performed during module map generation * 0.100.11 - Added support for Apex A3 specifically and Apex A3 controllers should now show FMM inputs on their device page * 0.100.10 - Added support for Apex A3 Pro & Jr detection, controller FMM inputs, and new level sensor type * 0.100.9 - Allow a manual option for GetCookie * 0.100.8 - Fix for Sensor #s greater than 9 (ex: EB832's 24v Port B) * 0.100.7 - Added support for WAV powerheads, updated handling of COR and Sicce pumps * 0.100.6 - Added support for SKY light modules and COR pumps * 0.100.5 - Added detection for ApexJr and hopefully more accurate recognition of other controllers except ApexEL * and can now enable Active Control at the same time as Child Devices so the child devices should be added with * their correct driver when created * 0.100.4 - Added DOS detection back in * 0.100.3 - Added detection of Sicce pumps and support for related child device * 0.100.2 - Added states and capabilities to handle controlling virtual switches and Trident tests * 0.100.1 - Added functions to control outlets, feeding cycles, feeder, and 24v ports * 0.100.0 - Included install instructions in the driver and a major rework of children to identify modules and use * specific drivers for each module type * 0.99.10 - Addition of Total Amps and Total Power attributes to parent and revision of version checking * 0.99.9 - Correction to adding child devices section and updates to driver version checking * 0.99.8 - Support added for Trident attributes and initial identification of Trident device * 0.99.7 - Correction for Amps handling with EB832, updates to driver version checking * 0.99.6 - Changed error connecting logging to be an error not a trace, commands to poll JSON and XML, added additional * attributes for Hostname, Updated, Serial, etc... * 0.99.5 - Change to scheduling jobs * 0.99.4 - Corrected a temperature data point that sent the units to a state variable * 0.99.3 - Added support for second pH on base and now sends State with user's names for Inputs to child devices * 0.99.2 - Added support for virtual outputs and updated scheduling to my newer style * 0.99.1 - Fixed some instances where state variable would be displayed as on when should be off * 0.99.0 - Further revision to data processing methods. * 0.98.1 - Added in identification of AI, Vortech, and DOS modules. * 0.98.0 - Rework of version control, logging, data processing, and added child device capability * 0.97 - Addition of 4 Apex-specific attributes for outputs (2 number & 2 strings) * 0.96 - Addition of 4 Apex-specific number attributes for inputs * 0.95 - Update to driver version checking section * 0.94 - Correction for number of inputs variable and Software event * 0.93 - Changed method to rely on the status.json file which has more detailed information and fixed the data refresh method * 0.92 - Correction to state changed for events * 0.91 - Removed deprecated code, cleaned up polling method, changes to update checking * 0.9 - Removed excess logging, clear states on update, DEPRECATED warning/alerts and presence features * 0.8 - Added the importUrl for the driver so it is easier to get the latest, changed update check schedule * 0.7 - Added Apex Software Version, list of all outlets & state, attribution correction, and comment cleanup * 0.6 - Minor changes and cleanup mostly to test the version checking * 0.5 - Added a check for the latest version of the driver (based on @Cobra's (Andrew Parker) update code) * 0.4 - Added inputs for probe targets, ranges, and provided alerts and warnings as a result * 0.3 - Made it possible to disable debug logging and adding more comments * 0.2 - Added ability to specify the name of the Apex sensor being read and other parameters * 0.1 - Initial inspiration/learning from @ogiewon's (Daniel Ogorchock) HTTP Momentary Switch & presence code from * @joelwetzel's (Joel Wetzel) Hubitat-HTTP-Presence-Sensor * * Thank you(s): * In Hubitat Forum: @Cobra, @ogiewon, @joelwetzel, @mircolino for their contributions and knowledge that made this driver possible * In Neptune Systems Forum: @chadg for providing a json sample for additional modules */ // Returns the driver name def DriverName(){ return "NeptuneSystemsApex" } // Returns the driver version def DriverVersion(){ return "0.100.19" } // Driver metadata metadata{ definition( name: "NeptuneSystemsApex", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/NeptuneSystemsApex.groovy" ){ // Attempting to indicate what capabilities the device should be capable of capability "Sensor" capability "Refresh" capability "Temperature Measurement" capability "pH Measurement" capability "VoltageMeasurement" capability "PowerMeter" capability "LiquidFlowRate" // Commands for the parent driver itself //command "DoSomething" // Test function, should be disabled prior to publishing driver command "GetCookie" // 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 // attribute "temperature", "number" // Temperature value received attribute "pH", "number" // pH value received attribute "pH2", "number" // pH2 value received attribute "ORP", "number" // ORP value received attribute "Salinity", "number" // Salinity value received attribute "Input Count", "number" // Number of inputs attribute "Output Count", "number" // Number of outputs attribute "voltage", "number" // Voltage value received from modules like EB832 attribute "power", "number" // Total power value received from modules like EB832 attribute "Hostname", "string" // Hostname returned by the Apex attribute "Updated", "string" // Date/time the Apex indicated the data was from attribute "Serial", "string" // Serial # of the Apex attribute "Apex Model", "string" // What model Apex was determined attribute "Total Amps from EB(s)", "number" // The total amperage from EB(s) attribute "Total Power from EB832(s)", "number" // The total power from EB832(s) attribute "Module Map", "map" // Attributes to allow users to set custom tests attribute "ApexInputNumber1", "number" attribute "ApexInputNumber2", "number" attribute "ApexInputNumber3", "number" attribute "ApexInputNumber4", "number" attribute "ApexOutputNumber1", "number" attribute "ApexOutputNumber2", "number" attribute "ApexOutputString1", "string" attribute "ApexOutputString2", "string" // Attributes for a Trident attribute "alk", "number" // alk value received attribute "ca", "number" // ca value received attribute "mg", "number" // mg value received attribute "Trident Selector 1", "string" attribute "Trident Selector 2", "string" } preferences{ section{ if( ShowAllPreferences || ShowAllPreferences == null ){ input( type: "string", name: "ApexURL", title: "Apex URL", required: true ) input( type: "enum", name: "RefreshRate", title: "Refresh Rate", required: false, multiple: false, options: [ "1 minute", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "1 hour", "3 hours", "Manual" ], defaultValue: "5 minutes" ) input( type: "string", name: "TempName", title: "Temperature Probe Name", required: false ) input( type: "string", name: "pHName", title: "pH Probe Name", required: false ) input( type: "string", name: "ORPName", title: "ORP Probe Name", required: false ) input( type: "string", name: "SalinityName", title: "Salinity Probe Name", required: false ) input( type: "string", name: "FlowRateName", title: "Water Flow Probe Name", required: false ) input( type: "bool", name: "ShowCustom", title: "Show Custom Attributes?", defaultValue: false ) if( ShowCustom ){ input( type: "string", name: "ApexInputNumber1Name", title: "ApexInputNumber1 Name", required: false ) input( type: "string", name: "ApexInputNumber2Name", title: "ApexInputNumber2 Name", required: false ) input( type: "string", name: "ApexInputNumber3Name", title: "ApexInputNumber3 Name", required: false ) input( type: "string", name: "ApexInputNumber4Name", title: "ApexInputNumber4 Name", required: false ) input( type: "string", name: "ApexOutputNumber1Name", title: "ApexOutputNumber1 Name", required: false ) input( type: "string", name: "ApexOutputNumber2Name", title: "ApexOutputNumber2 Name", required: false ) input( type: "string", name: "ApexOutputString1Name", title: "ApexOutputString1 Name", required: false ) input( type: "string", name: "ApexOutputString2Name", title: "ApexOutputString2 Name", required: false ) } input( type: "bool", name: "ProvideAllInputs", title: "Provide events for all inputs", required: false ) input( type: "bool", name: "ProvideAllOutputs", title: "Provide events for all outputs", required: false ) input( type: "bool", name: "ChildrenEnabled", title: "Enable Child Devices?", description: "Once enabled, a child device will be made for added per module.", required: false, defaultValue: false ) input( type: "bool", name: "ActiveControl", title: "Actively Control Modules", description: "Requires Child Devices be Enabled.
WARNING: This acts as manual control of the features involved and could interfere with the Apex\'s control.
", required: false, defaultValue: false ) 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?", required: false, defaultValue: true ) } else { input( type: "bool", name: "ShowAllPreferences", title: "Show All Preferences?", required: false, defaultValue: true ) } } } } // Test code area... to be disabled before publishing def DoSomething(){ } // updated is called whenever device parameters are saved // It sets the current version of the driver and sets some basic settings def updated(){ if( LogType == null ){ LogType = "Info" } if( !ChildrenEnabled && getChildDevices() != null ){ // Delete all children Logging( "Child devices disabled, but had been enabled, deleting child devices", 3 ) getChildDevices().each{ deleteChildDevice( it.deviceNetworkId ) } } if( RefreshRate == null ){ RefreshRate = "5 minutes" } // Get times to use for scheduling def Hour = ( new Date().format( "h" ) as int ) def Minute = ( new Date().format( "m" ) as int ) def Second = ( new Date().format( "s" ) as int ) // Check what the refresh rate is set for then run it switch( RefreshRate ){ case "5 minutes": // Schedule the refresh check for every 5 minutes schedule( "${ Second } 0/5 * ? * *", "refresh" ) break case "10 minutes": // Schedule the refresh check for every 10 minutes schedule( "${ Second } 0/10 * ? * *", "refresh" ) break case "15 minutes": // Schedule the refresh check for every 15 minutes schedule( "${ Second } 0/15 * ? * *", "refresh" ) break case "30 minutes": // Schedule the refresh check for every 30 minutes schedule( "${ Second } 0/30 * ? * *", "refresh" ) break case "1 hour": // Schedule the refresh check for every hour schedule( "${ Second } ${ Minute } * ? * *", "refresh" ) break case "3 hours": // Schedule the refresh check for every 3 hours schedule( "${ Second } ${ Minute } 0/3 ? * *", "refresh" ) break case "Manual": unschedule( "refresh" ) break } Logging( "Refresh rate: ${ RefreshRate }", 3 ) // Set the driver name and version before update checking is scheduled if( state."Driver Name" != null ){ state.remove( "Driver Name" ) state.remove( "Driver Version" ) device.deleteCurrentState( "Driver Name" ) device.deleteCurrentState( "Driver Version" ) } ProcessState( "DriverName", "${ DriverName() }" ) ProcessState( "DriverVersion", "${ DriverVersion() }" ) // Schedule the daily driver version check schedule( "${ Second } ${ Minute } ${ Hour } ? * *", "CheckForUpdate" ) // If children and active control are enabled, schedule regular cookie checks, currently every hour if( ChildrenEnabled && ActiveControl ){ schedule( "${ Second } ${ Minute } * ? * *", "GetCookie" ) if( state.Cookie == null ){ GetCookie() } } else { // If children and/or active control are not enabled, remove any regular cookie check unschedule( "GetCookie" ) } Logging( "Updated", 2 ) } // refresh polls the Apex def refresh(){ PollJSON() } // Processes data for modules def ProcessModuleData( Data ){ def TempModules = [] def ListDID = [] //Logging( "ProcessModuleData = ${ Data }", 4 ) Json = parseJson( Data ) switch( Json.istat.serial.split( ":" )[ 0 ] ){ case "AC4": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "ApexClassic" ] ) } break case "AC4J": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "ApexJr" ] ) } break case "AC5": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "Apex2016" ] ) } break case "AC5L": case "AC5EL": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "ApexEL" ] ) } break case "AC6": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "ApexA3Pro" ] ) } break case "AC6L": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "ApexA3" ] ) } break case "AC6J": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "ApexA3Jr" ] ) } break default: if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "base", Type : "Apex${ Json.istat.serial.split( ":" )[ 0 ] }" ] ) Logging( "Unknown Apex controller type ${ Json.istat.serial.split( ":" )[ 0 ] }, please let developer know", 2 ) } break } Json.istat.inputs.each{ def TempDID = it.did.split( "_" )[ 0 ] if( ListDID.indexOf( TempDID ) == -1 ){ ListDID << TempDID } def TempItem = it.did.split( "_" )[ 1 ] def TempType = it.type if( TempDID == "Ctrl" ){ if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Virtual" ] ) } } //if( it.did == "base_Cond" ){ // if( TempModules.find{ it.DID == TempDID } == null ){ // TempModules.add( [ DID : "${ TempDID }", Type : "Apex2016" ] ) // } //} if( TempItem == "P0" ){ if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "EB832" ] ) } } if( TempItem == "1" ){ if( ( TempModules.find{ it.DID == "base" }.Type == "ApexA3Pro" ) || ( TempModules.find{ it.DID == "base" }.Type == "ApexA3" ) || ( TempModules.find{ it.DID == "base" }.Type == "ApexA3Jr" ) ){ if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : ( TempModules.find{ it.DID == "base" }.Type ) ] ) } } switch( TempType ){ case "pH": case "ORP": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "PM1" ] ) } break case "Cond": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "PM2" ] ) } break case "DO": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "PM3" ] ) } break case "ASM": case "PAR": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type: "ASM" ] ) } break case "ca": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Trident" ] ) } break case "dos": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "DOS" ] ) } break } } } Json.istat.outputs.each{ def TempDID = it.did.split( "_" )[ 0 ] def TempItem = it.did.split( "_" )[ 1 ] def TempType = it.type if( ListDID.indexOf( TempDID ) == -1 ){ ListDID << TempDID } if( TempDID == "Ctrl" ){ if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Virtual" ] ) } } switch( TempType ){ case "afs": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "AFS" ] ) } break case "dos": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "DOS" ] ) } break case "ai": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "AI" ] ) } break case "MXMPump|Ecotech|Vortech": case "vortech": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Vortech" ] ) } break case "MXMPump|Ecotech|Vectra": case "vectra": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Vectra" ] ) } break case "iotaPump|Sicce|Syncra": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Sicce" ] ) } break case "MXMLight|Ecotech|30G6PL": case "MXMLight|Ecotech|30G6P": case "MXMLight|Ecotech|15G6PL": case "MXMLight|Ecotech|15G6P": case "MXMLight|Ecotech|30G5PL": case "MXMLight|Ecotech|30G5P": case "MXMLight|Ecotech|15G5PL": case "MXMLight|Ecotech|15G5P": case "MXMLight|Ecotech|30G6L": case "MXMLight|Ecotech|30G6": case "MXMLight|Ecotech|15G6L": case "MXMLight|Ecotech|15G6": case "MXMLight|Ecotech|30G5L": case "MXMLight|Ecotech|30G5": case "MXMLight|Ecotech|15G5L": case "MXMLight|Ecotech|15G5": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Radion" ] ) } break case "virtual": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "Virtual" ] ) } break case "dos": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "DOS" ] ) } break case "sky": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "SKY" ] ) } break case "cor|15": case "cor|20": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "COR" ] ) } break case "wav": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "WAV" ] ) } break } if( TempItem == "1" ){ switch( TempType ){ case "24v": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "FMM" ] ) } break case "variable": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "VDM" ] ) } break } } if( TempItem == "4" ){ switch( TempType ){ case "outlet": if( TempModules.find{ it.DID == TempDID } == null ){ TempModules.add( [ DID : "${ TempDID }", Type : "EB4" ] ) } break } } if( TempItem == "8" && TempType == "outlet" ){ if( TempModules.find{ it.DID == TempDID } ){ if( TempModules.find{ it.DID == TempDID && it.Type == "EB4" } ){ def ModuleRework = [] TempModules.each{ if( it.Type == "EB4" ){ ModuleRework.add( [ DID : "${ it.DID }", Type : "EB8" ] ) Logging( "EB4 module changed to EB8 = ${ it.DID }", 4 ) } else { if( ModuleRework.indexOf( it.DID ) == -1 ){ ModuleRework.add( [ DID : "${ it.DID }", Type : "${ it.Type }" ] ) } } } TempModules = ModuleRework } } else { Logging( "${ TempDID } not found, adding as EB8 to begin with", 4 ) TempModules.add( [ DID : "${ TempDID }", Type : "EB8" ] ) } } if( TempItem == "9" && TempType == "24v" ){ if( TempModules.find{ it.DID == TempDID } ){ if( TempModules.find{ it.DID == TempDID && ( it.Type == "EB4" || it.Type == "EB8" ) } ){ def ModuleRework = [] TempModules.each{ if( it.Type == "EB4" ){ ModuleRework.add( [ DID : "${ it.DID }", Type : "EB832" ] ) Logging( "EB4 module changed to EB832 = ${ it.DID }", 4 ) } else if( it.Type == "EB8" ){ ModuleRework.add( [ DID : "${ it.DID }", Type : "EB832" ] ) Logging( "EB9 module changed to EB832 = ${ it.DID }", 4 ) } else { if( ModuleRework.indexOf( it.DID ) == -1 ){ ModuleRework.add( [ DID : "${ it.DID }", Type : "${ it.Type }" ] ) } } } TempModules = ModuleRework } } else { Logging( "${ TempDID } not found, adding as an EB832 to begin with", 4 ) TempModules.add( [ DID : "${ TempDID }", Type : "EB832" ] ) } } } ProcessState( "Module Map", TempModules ) Logging( "List of DIDs = ${ ListDID }", 4 ) Logging( "Module Map = ${ state.'Module Map' }", 4 ) ProcessState( "Modules Checked", new Date() ) } //Poll Apex for JSON def PollJSON(){ Logging( "Polling Apex for JSON data.", 3 ) def Params = [ uri: "${ ApexURL }/cgi-bin/status.json", contentType: "application/json" ] asynchttpGet( "ReceiveJSONData", Params ) } // Handles getting the actual data from a JSON query def ReceiveJSONData( resp, data ){ switch( resp.getStatus() ){ case 200: Logging( "JSON Response = ${ resp.data }", 4 ) ProcessModuleData( resp.data ) Json = parseJson( resp.data ) def Device = "${ state.'Module Map'.find{ it.DID == "base" }.Type } #base" // Gets the serial number if( Json.istat.serial != null ){ ProcessEvent( "Serial", Json.istat.serial ) PostEventToChild( "${ Device }", "Serial", Json.istat.serial ) } // Gets the Hostname if( Json.istat.hostname != null ){ ProcessEvent( "Hostname", Json.istat.hostname ) PostEventToChild( "${ Device }", "Hostname", Json.istat.hostname ) } // Gets the software version if( Json.istat.software != null ){ ProcessEvent( "Software", Json.istat.software ) PostEventToChild( "${ Device }", "Software", Json.istat.software ) } // Gets the date/time that the Apex provided the data if( Json.istat.date != null ){ ProcessState( "Updated", ConvertEpochToDate( Json.istat.date ) ) } // Gets the date/time power last failed if( Json.istat.power.failed != null ){ ProcessState( "Power Failure", ConvertEpochToDate( Json.istat.power.failed ) ) PostEventToChild( "${ Device }", "Power Failure", ConvertEpochToDate( Json.istat.power.failed ) ) } // Gets the date/time power was last restored if( Json.istat.power.restored != null ){ ProcessState( "Power Restored", ConvertEpochToDate( Json.istat.power.restored ) ) PostEventToChild( "${ Device }", "Power Restored", ConvertEpochToDate( Json.istat.power.restored ) ) } ProcessJSONInputs( Json.istat.inputs ) ProcessJSONOutputs( Json.istat.outputs ) break default: Logging( "Error connecting to Apex: ${ resp.status }", 5 ) break } } // ProcessJSONInputs receives all the inputs from the Apex data and processes them def ProcessJSONInputs( Inputs ){ // Gets the list of inputs and their current state def NumberOfInputs = Inputs.size() as Integer Logging( "Number of Inputs = ${ NumberOfInputs }", 4 ) ProcessState( "Input Count", NumberOfInputs ) def TotalPower = 0 as float // Created to sum power value if it starts to show up from any EB832 def TotalAmps = 0 as float // Created to sum amps value if it starts to show up from any EB def ChildAmps = [] //def ChildType = DetermineType Inputs.each{ def Handled = false def TempDID = it.did TempDID = TempDID.split( "_" ) if( state.'Module Map'.find{ it.DID == TempDID[ 0 ] } != null ){ if( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type != null ){ def Device = "${ state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type } #${ TempDID[ 0 ] }" def SensorNumber = TempDID[ 1 ][-1..-1] Logging( "Input: ${ it.name } = ${ it.value }", 4 ) switch( it.did ){ case "base_Temp": ProcessState( "${ it.name }", it.value ) PostEventToChild( "${ Device }", "temperature", it.value, "°${ location.getTemperatureScale() }" ) Handled = true break case "base_pH": ProcessState( "${ it.name }", it.value ) PostEventToChild( "${ Device }", "pH", it.value ) Handled = true break case "base_pH2": ProcessState( "${ it.name }", it.value ) PostEventToChild( "${ Device }", "pH2", it.value ) Handled = true break case "base_ORP": ProcessState( "${ it.name }", it.value ) PostEventToChild( "${ Device }", "ORP", it.value ) Handled = true break case "base_Cond": ProcessState( "${ it.name }", it.value ) PostEventToChild( "${ Device }", "Salinity", it.value ) Handled = true break case ~/base_I([1-6])/: PostStateToChild( "${ Device }", "Switch Input ${ SensorNumber } Name", it.name ) if( it.value == 0 ){ ProcessState( "${ it.name }", "Open" ) PostEventToChild( "${ Device }", "Switch Input ${ SensorNumber }", "Open" ) } else { ProcessState( "${ it.name }", "Closed" ) PostEventToChild( "${ Device }", "Switch Input ${ SensorNumber }", "Closed" ) } Handled = true break case ~/1_([1-4])/: if( ( state.'Module Map'.find{ it.DID == "base" }.Type == "ApexA3Pro" ) || ( state.'Module Map'.find{ it.DID == "base" }.Type == "ApexA3" ) || ( state.'Module Map'.find{ it.DID == "base" }.Type == "ApexA3Jr" ) ){ Device = "${ state.'Module Map'.find{ it.DID == "base" }.Type } #base" } switch( it.type ){ case "digital": PostStateToChild( "${ Device }", "FMM Input ${ SensorNumber } Name", it.name ) if( it.value == 0 ){ ProcessState( "${ Device } FMM Input ${ SensorNumber }", "Open" ) PostEventToChild( "${ Device }", "FMM Input ${ SensorNumber }", "Open" ) PostStateToChild( "${ Device }", "${ it.name }", "Open" ) } else { ProcessState( "${ Device } FMM Input ${ SensorNumber }", "Closed" ) PostEventToChild( "${ Device }", "FMM Input ${ SensorNumber }", "Closed" ) PostStateToChild( "${ Device }", "${ it.name }", "Closed" ) } Handled = true break case "in": // New level sensor reporting type PostStateToChild( "${ Device }", "FMM Input ${ SensorNumber } Name", it.name ) ProcessState( "${ Device } FMM Input ${ SensorNumber }", it.value ) PostEventToChild( "${ Device }", "FMM Input ${ SensorNumber }", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "gph": if( location.getTemperatureScale() == "C" ){ def Value = ( ( it.value as float ) * 3.78541 ) ProcessState( "${ Device }_FMM_${ SensorNumber }_LPH", Value ) PostEventToChild( "${ Device }", "FMM_${ SensorNumber }_rate", ( Value / 60 ), "LPM" ) PostStateToChild( "${ Device }", "FMM_${ SensorNumber }_LPH", Value ) } else { ProcessState( "${ Device } GPH", it.value ) PostEventToChild( "${ Device }", "FMM_${ SensorNumber }_rate", ( ( it.value as float ) / 60 ), "GPM" ) PostStateToChild( "${ Device }", "FMM_${ SensorNumber }_GPH", it.value ) } Handled = true break } break } if( !Handled ){ switch( it.type ){ case "Temp": ProcessState( "${ Device } Temp", it.value ) PostEventToChild( "${ Device }", "temperature", it.value, "°${ location.getTemperatureScale() }" ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "pH": ProcessState( "${ Device } pH", it.value ) PostEventToChild( "${ Device }", "pH", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "ca": ProcessState( "${ Device } ca", it.value ) PostEventToChild( "${ Device }", "ca", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "alk": ProcessState( "${ Device } alk", it.value ) PostEventToChild( "${ Device }", "alk", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "mg": ProcessState( "${ Device } mg", it.value ) PostEventToChild( "${ Device }", "mg", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "ORP": ProcessState( "${ Device } ORP", it.value ) PostEventToChild( "${ Device }", "ORP", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "Cond": ProcessState( "${ Device } Salinity", it.value ) PostEventToChild( "${ Device }", "Salinity", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "DO": ProcessState( "${ Device } Dissolved Oxygen", it.value ) PostEventToChild( "${ Device }", "Dissolved Oxygen", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "PAR": ProcessState( "${ Device } PAR", it.value ) PostEventToChild( "${ Device }", "PAR", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "volts": ProcessState( "${ Device } Voltage", it.value ) PostEventToChild( "${ Device }", "voltage", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "gph": if( location.getTemperatureScale() == "C" ){ def Value = ( ( it.value as float ) * 3.78541 ) ProcessState( "${ Device }_LPH", Value ) PostEventToChild( "${ Device }", "rate", ( Value / 60 ), "LPM" ) PostStateToChild( "${ Device }", "LPH", Value ) } else { ProcessState( "${ Device }_GPH", it.value ) PostEventToChild( "${ Device }", "rate", ( ( it.value as float ) / 60 ), "GPM" ) PostStateToChild( "${ Device }", "GPH", it.value ) } Handled = true break case "digital": PostStateToChild( "${ Device }", "Switch Input ${ SensorNumber } Name", it.name ) if( it.value == 0 ){ ProcessState( "${ Device } Switch Input ${ SensorNumber }", "Open" ) PostEventToChild( "${ Device }", "Switch Input ${ SensorNumber }", "Open" ) PostStateToChild( "${ Device }", "${ it.name }", "Open" ) } else { ProcessState( "${ Device } Switch Input ${ SensorNumber }", "Closed" ) PostEventToChild( "${ Device }", "Switch Input ${ SensorNumber }", "Closed" ) PostStateToChild( "${ Device }", "${ it.name }", "Closed" ) } Handled = true break case "Amps": TotalAmps = TotalAmps + it.value if( ChildAmps[TempDID[ 0 ] as int ] == null ){ ChildAmps[ TempDID[ 0 ] as int ] = it.value } else { ChildAmps[ TempDID[ 0 ] as int ] += it.value } PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "pwr": TotalPower = TotalPower + it.value ProcessState( "${ Device } Power", it.value ) PostEventToChild( "${ Device }", "power", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break case "in": // New level sensor reporting type PostStateToChild( "${ Device }", "Switch Input ${ SensorNumber } Name", it.name ) ProcessState( "${ Device } Switch Input ${ SensorNumber }", it.value ) PostEventToChild( "${ Device }", "Switch Input ${ SensorNumber }", it.value ) PostStateToChild( "${ Device }", "${ it.name }", it.value ) Handled = true break default: Logging( "Unknown input type: ${ Device }: ${ it.type } = ${ it.value }", 3 ) PostStateToChild( "${ Device }", "${ it.name }", "${ it.value }" ) break } } // Custom attribute identification switch( it.name ){ case "${ TempName }": ProcessEvent( "temperature", ConvertTemperature( "F", it.value), "°${ location.getTemperatureScale() }" ) break case "${ pHName }": ProcessEvent( "pH", it.value ) break case "${ ORPName }": ProcessEvent( "ORP", it.value ) break case "${ SalinityName }": ProcessEvent( "Salinity", it.value ) break case "${ FlowRateName }": if( location.getTemperatureScale() == "C" ){ def Value = ( ( it.value as float ) * 3.78541 ) ProcessEvent( "${ Device }", "rate", ( Value / 60 ), "LPM" ) } else { ProcessEvent( "${ Device }", "rate", ( ( it.value as float ) / 60 ), "GPM" ) } break case "${ ApexInputNumber1Name }": ProcessEvent( "ApexInputNumber1", it.value ) break case "${ ApexInputNumber2Name }": ProcessEvent( "ApexInputNumber2", it.value ) break case "${ ApexInputNumber3Name }": ProcessEvent( "ApexInputNumber3", it.value ) break case "${ ApexInputNumber4Name }": ProcessEvent( "ApexInputNumber4", it.value ) break default: if( ProvideAllInputs ){ ProcessEvent( "${ it.name }", it.value ) } break } } } } ProcessEvent( "Total Amps from EB(s)", TotalAmps ) ProcessEvent( "Total Power from EB832(s)", TotalPower ) if( ChildrenEnabled ){ def Count = 0 ChildAmps.each{ if( it != null ){ def Device = "${ state.'Module Map'.find{ it.DID == "${ Count }" }.Type } #${ Count }" ProcessState( "${ Device } Amps", it ) PostEventToChild( "${ Device }", "Amps", it ) } Count ++ } } } // ProcessJSONOutputs receives all the outputs from the Apex data and processes them def ProcessJSONOutputs( Outputs, String ActiveResponse = "false" ){ // Gets the list of inputs and their current state def NumberOfOutputs = Outputs.size() as Integer if( ActiveResponse == "false " ){ Logging( "Number of Outputs = ${ NumberOfOutputs }", 4 ) ProcessState( "Output Count", NumberOfInputs ) } Outputs.each{ def Handled = false def TempDID = it.did TempDID = TempDID.split( "_" ) if( state.'Module Map'.find{ it.DID == TempDID[ 0 ] } != null ){ if( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type != null ){ def Device = "${ state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type } #${ TempDID[ 0 ] }" def SensorNumber //= TempDID[ 1 ][-1..-1] if( TempDID[ 1 ].isInteger() ){ SensorNumber = TempDID[ 1 ] as int } else if( TempDID[ 1 ][-1..-1].isInteger() ){ SensorNumber = TempDID[ 1 ][-1..-1] } else { SensorNumber = TempDID[ 1 ] } Logging( "Output: ${ it.name } ${ it.did }: (Sensor # ${ SensorNumber }) = ${ it.status[ 0 ] }", 4 ) switch( it.did ){ case "base_Alarm": ProcessState( "Base Alarm", it.status[ 0 ] ) PostEventToChild( "${ Device }", "Alarm", it.status[ 0 ] ) Handled = true break case "base_Warn": ProcessState( "Base Warn", it.status[ 0 ] ) PostEventToChild( "${ Device }", "Warn", it.status[ 0 ] ) Handled = true break case "base_email": ProcessState( "Base Email", it.status[ 0 ] ) PostEventToChild( "${ Device }", "Email", it.status[ 0 ] ) Handled = true break case ~/base_Var([1-4])/: ProcessState( "Variable Output ${ SensorNumber }", it.status[ 0 ] ) PostEventToChild( "${ Device }", "Variable Output ${ SensorNumber }", it.status[ 1 ] ) Handled = true break } if( !Handled ){ switch( it.type ){ case "24v": // Only one example so far if( SensorNumber == "1" ){ PostStateToChild( "${ Device }", "24v Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( it.status[ 0 ] == "AON" ){ ProcessState( "${ Device } 24v", "On - Auto" ) PostEventToChild( "${ Device }", "24v", "On - Auto" ) } else if( it.status[ 0 ] == "ON" ){ ProcessState( "${ Device } 24v", "On" ) PostEventToChild( "${ Device }", "24v", "On" ) } else if( it.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } 24v", "Off - Auto" ) PostEventToChild( "${ Device }", "24v", "Off - Auto" ) } else if( it.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } 24v", "Off" ) PostEventToChild( "${ Device }", "24v", "Off" ) } Handled = true } else if( SensorNumber == "9" ){ PostStateToChild( "${ Device }", "24v A Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( it.status[ 0 ] == "AON" ){ ProcessState( "${ Device } 24v A", "On - Auto" ) PostEventToChild( "${ Device }", "24v A", "On - Auto" ) } else if( it.status[ 0 ] == "ON" ){ ProcessState( "${ Device } 24v A", "On" ) PostEventToChild( "${ Device }", "24v A", "On" ) } else if( it.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } 24v A", "Off - Auto" ) PostEventToChild( "${ Device }", "24v A", "Off - Auto" ) } else if( it.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } 24v A", "Off" ) PostEventToChild( "${ Device }", "24v A", "Off" ) } Handled = true } else if( SensorNumber == "10" ){ PostStateToChild( "${ Device }", "24v B Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( it.status[ 0 ] == "AON" ){ ProcessState( "${ Device } 24v B", "On - Auto" ) PostEventToChild( "${ Device }", "24v B", "On - Auto" ) } else if( it.status[ 0 ] == "ON" ){ ProcessState( "${ Device } 24v B", "On" ) PostEventToChild( "${ Device }", "24v B", "On" ) } else if( it.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } 24v B", "Off - Auto" ) PostEventToChild( "${ Device }", "24v B", "Off - Auto" ) } else if( it.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } 24v B", "Off" ) PostEventToChild( "${ Device }", "24v B", "Off" ) } Handled = true } break case "outlet": PostStateToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] } Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( it.status[ 0 ] == "AON" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "On - Auto" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "On - Auto" ) } else if( it.status[ 0 ] == "ON" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "On" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "On" ) } else if( it.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "Off - Auto" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "Off - Auto" ) } else if( it.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "Off" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "Off" ) } Handled = true break case "dos": PostStateToChild( "${ Device }", "Output ${ TempDID[ 1 ] } Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( ( it.status[ 0 ] == "AON" ) || ( it.status[ 0 ] == "ON" ) ){ ProcessState( "${ Device } Output ${ TempDID[ 1 ] }", "on" ) PostEventToChild( "${ Device }", "Output ${ TempDID[ 1 ] }", "on" ) } else { ProcessState( "${ Device } Output ${ TempDID[ 1 ] }", "off" ) PostEventToChild( "${ Device }", "Output ${ TempDID[ 1 ] }", "off" ) } Handled = true break case "ai": PostStateToChild( "${ Device }", "Output ${ TempDID[ 1 ] } Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( ( it.status[ 0 ] == "AON" ) || ( it.status[ 0 ] == "ON" ) ){ ProcessState( "${ Device } AI ${ TempDID[ 1 ] }", "on" ) PostEventToChild( "${ Device }", "AI ${ TempDID[ 1 ] }", "on" ) } else if( it.status[ 0 ] == "TBL" ){ ProcessState( "${ Device } AI ${ TempDID[ 1 ] }", it.status[ 1 ] ) PostEventToChild( "${ Device }", "AI ${ TempDID[ 1 ] }", it.status[ 1 ] ) } else { ProcessState( "${ Device } AI ${ TempDID[ 1 ] }", "off" ) PostEventToChild( "${ Device }", "AI ${ TempDID[ 1 ] }", "off" ) } Handled = true break case "MXMLight|Ecotech|30G6PL": case "MXMLight|Ecotech|30G6P": case "MXMLight|Ecotech|15G6PL": case "MXMLight|Ecotech|15G6P": case "MXMLight|Ecotech|30G5PL": case "MXMLight|Ecotech|30G5P": case "MXMLight|Ecotech|15G5PL": case "MXMLight|Ecotech|15G5P": case "MXMLight|Ecotech|30G6L": case "MXMLight|Ecotech|30G6": case "MXMLight|Ecotech|15G6L": case "MXMLight|Ecotech|15G6": case "MXMLight|Ecotech|30G5L": case "MXMLight|Ecotech|30G5": case "MXMLight|Ecotech|15G5L": case "MXMLight|Ecotech|15G5": PostStateToChild( "${ Device }", "Output ${ TempDID[ 1 ] } Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( ( it.status[ 0 ] == "AON" ) || ( it.status[ 0 ] == "ON" ) ){ ProcessState( "${ Device } Radion ${ TempDID[ 1 ] }", "on" ) PostEventToChild( "${ Device }", "Radion ${ TempDID[ 1 ] }", "on" ) } else if( it.status[ 0 ] == "TBL" ){ ProcessState( "${ Device } Radion ${ TempDID[ 1 ] }", it.status[ 1 ] ) PostEventToChild( "${ Device }", "Radion ${ TempDID[ 1 ] }", it.status[ 1 ] ) } else { ProcessState( "${ Device } Radion ${ TempDID[ 1 ] }", "off" ) PostEventToChild( "${ Device }", "Radion ${ TempDID[ 1 ] }", "off" ) } Handled = true break case "MXMPump|Ecotech|Vortech": case "vortech": PostStateToChild( "${ Device }", "Output ${ TempDID[ 1 ] } Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( ( it.status[ 0 ] == "AON" ) || ( it.status[ 0 ] == "ON" ) ){ ProcessState( "${ Device } Vortech ${ TempDID[ 1 ] }", "on" ) PostEventToChild( "${ Device }", "Vortech ${ TempDID[ 1 ] }", "on" ) } else if( it.status[ 0 ] == "TBL" ){ ProcessState( "${ Device } Vortech ${ TempDID[ 1 ] }", it.status[ 1 ] ) PostEventToChild( "${ Device }", "Vortech ${ TempDID[ 1 ] }", it.status[ 1 ] ) } else { ProcessState( "${ Device } Vortech ${ TempDID[ 1 ] }", "off" ) PostEventToChild( "${ Device }", "Vortech ${ TempDID[ 1 ] }", "off" ) } Handled = true break case "MXMPump|Ecotech|Vectra": PostStateToChild( "${ Device }", "Output ${ TempDID[ 1 ] } Name", "${ it.name }" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) if( ( it.status[ 0 ] == "AON" ) || ( it.status[ 0 ] == "ON" ) ){ ProcessState( "${ Device } Vectra ${ TempDID[ 1 ] }", "on" ) PostEventToChild( "${ Device }", "Vectra ${ TempDID[ 1 ] }", "on" ) } else if( it.status[ 0 ] == "TBL" ){ ProcessState( "${ Device } Vectra ${ TempDID[ 1 ] }", it.status[ 1 ] ) PostEventToChild( "${ Device }", "Vectra ${ TempDID[ 1 ] }", it.status[ 1 ] ) } else { ProcessState( "${ Device } Vectra ${ TempDID[ 1 ] }", "off" ) PostEventToChild( "${ Device }", "Vectra ${ TempDID[ 1 ] }", "off" ) } Handled = true break case "afs": if( it.status[ 0 ] == "AON" ){ ProcessState( "${ Device } AFS", "On - Auto" ) PostEventToChild( "${ Device }", "switch", "on" ) PostEventToChild( "${ Device }", "Feeder Mode", "On - Auto" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) } else if( it.status[ 0 ] == "ON" ){ ProcessState( "${ Device } AFS", "On" ) PostEventToChild( "${ Device }", "switch", "on" ) PostEventToChild( "${ Device }", "Feeder Mode", "On" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) } else if( it.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } AFS", "Off - Auto" ) PostEventToChild( "${ Device }", "switch", "off" ) PostEventToChild( "${ Device }", "Feeder Mode", "Off - Auto" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) } else if( it.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } AFS", "Off" ) PostEventToChild( "${ Device }", "switch", "off" ) PostEventToChild( "${ Device }", "Feeder Mode", "Off" ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) } Handled = true break case "variable": ProcessState( "${ Device } VDM ${ SensorNumber } Intensity", it.intensity ) PostStateToChild( "${ Device }", "Variable ${ SensorNumber } Name", "${ it.name }" ) PostEventToChild( "${ Device }", "Variable ${ SensorNumber } Intensity", it.intensity ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) Handled = true break case "serial": PostStateToChild( "${ Device }", "Serial ${ SensorNumber - 4 } Name", "${ it.name }" ) if( ( it.status[ 0 ] == "AON" ) || ( it.status[ 0 ] == "ON" ) ){ ProcessState( "${ Device } Serial ${ SensorNumber - 4 }", "on" ) PostEventToChild( "${ Device }", "Serial ${ SensorNumber - 4 } Switch", "on" ) } else { ProcessState( "${ Device } Serial ${ SensorNumber - 4 }", "off" ) PostEventToChild( "${ Device }", "Serial ${ SensorNumber - 4 } Switch", "off" ) } ProcessState( "${ Device } Serial ${ SensorNumber - 4 } Intensity", it.intensity ) PostEventToChild( "${ Device }", "Serial ${ SensorNumber - 4 } Intensity", it.intensity ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) Handled = true break case "sky": ProcessState( "${ Device } Sky Intensity", it.intensity ) PostStateToChild( "${ Device }", "Sky Name", "${ it.name }" ) PostEventToChild( "${ Device }", "Sky Intensity", it.intensity ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) Handled = true break case "moon": ProcessState( "${ Device } Moon Intensity", it.intensity ) PostStateToChild( "${ Device }", "Moon Name", "${ it.name }" ) PostEventToChild( "${ Device }", "Moon Intensity", it.intensity ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) Handled = true break case "iotaPump|Sicce|Syncra": case "cor|15": case "cor|20": case "wav": switch( it.status[ 0 ] ){ case "AON": ProcessState( "${ Device } switch", "On" ) PostEventToChild( "${ Device }", "switch", "On - Auto" ) break case "ON": ProcessState( "${ Device } switch", "On" ) PostEventToChild( "${ Device }", "switch", "On" ) break case "AOF": ProcessState( "${ Device } switch", "Off" ) PostEventToChild( "${ Device }", "switch", "Off - Auto" ) break case "OFF": ProcessState( "${ Device } switch", "Off" ) PostEventToChild( "${ Device }", "switch", "Off" ) break case "TBL": ProcessState( "${ Device } level", it.intensity ) PostEventToChild( "${ Device }", "level", it.intensity ) break } ProcessState( "${ Device } level", it.intensity ) PostEventToChild( "${ Device }", "level", it.intensity ) Handled = true break case "virtual": PostStateToChild( "${ Device }", "Virtual A${ SensorNumber - 4 } Name", "${ it.name }" ) if( it.status[ 0 ] == "AON" ){ ProcessState( "${ Device } Virtual A${ SensorNumber - 4 }", "on" ) PostEventToChild( "${ Device }", "Virtual A${ SensorNumber - 4 } Switch", "On - Auto" ) } else if( it.status[ 0 ] == "ON" ){ ProcessState( "${ Device } Virtual A${ SensorNumber - 4 }", "on" ) PostEventToChild( "${ Device }", "Virtual A${ SensorNumber - 4 } Switch", "On" ) } else if( it.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } Virtual A${ SensorNumber - 4 }", "off" ) PostEventToChild( "${ Device }", "Virtual A${ SensorNumber - 4 } Switch", "Off - Auto" ) } else if( it.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } Virtual A${ SensorNumber - 4 }", "off" ) PostEventToChild( "${ Device }", "Virtual A${ SensorNumber - 4 } Switch", "Off" ) } else { ProcessState( "${ Device } Virtual A${ SensorNumber - 4 }", "${ it.status[ 0 ] }" ) PostEventToChild( "${ Device }", "Virtual A${ SensorNumber - 4 } Switch", "${ it.status[ 0 ] }" ) } PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) Handled = true break case "selector": ProcessState( "${ Device } Trident Selector ${ ( SensorNumber - 2 ) }", it.status[ 0 ] ) PostStateToChild( "${ Device }", "Trident Selector ${ ( SensorNumber - 2 ) } Name", "${ it.name }" ) PostEventToChild( "${ Device }", "Trident Selector ${ ( SensorNumber - 2 ) }", it.status[ 0 ] ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) Handled = true break case "alert": ProcessState( "${ Device } ${ it.name } }", it.status[ 0 ] ) PostStateToChild( "${ Device }", "${ it.name }", it.status[ 0 ] ) Handled = true break default: Logging( "Unknown output type: ${ Device }: ${ it.type } = ${ it.status[ 0 ]}", 3 ) PostStateToChild( "${ Device }", "${ it.name }", "${ it.status[ 0 ] }" ) break } } } } // Custom attribute identification switch( it.name ){ case "${ ApexOutputNumber1Name }": ProcessEvent( "ApexOutputNumber1", it.value ) break case "${ ApexOutputNumber1Name }": ProcessEvent( "ApexOutputNumber2", it.value ) break case "${ ApexOutputString1Name }": ProcessEvent( "ApexOutputString1", it.value ) break case "${ ApexOutputString2Name }": ProcessEvent( "ApexOutputString2", it.value ) break default: if( ProvideAllInputs ){ ProcessEvent( "${ it.name }", it.value ) } break } } } // Active Control Functions // Attempts to get a login cookie for active control functions def GetCookie(){ def Temp1 = ApexURL.split( "//" )[ 1 ] def Username = Temp1.split( ":" )[ 0 ] def Password = Temp1.split( ":" )[ 1 ].split( "@" )[ 0 ] if( Username != null && Password != null ){ def URL = ApexURL.split( "@" )[ 1 ] def Params = [ uri: "http://${ URL }/rest/login", contentType: "application/json", body:"{\"login\":\"${ Username }\",\"password\":\"${ Password }\",\"remember_me\":false}" ] Logging( "GetCookie Params = ${ Params }", 4 ) try{ httpPost( Params ){ resp -> switch( resp.getStatus() ){ case 200: resp.getHeaders().each{ if( it.value.split( '=' )[ 0 ].toString() == "connect.sid" ){ ProcessState( "Cookie", resp.getHeaders().'Set-Cookie' ) } //Logging( "GetCookie Response Header: ${ it }", 4 ) } break } } } catch( Exception e ){ Logging( "Exception when getting cookie: ${ e }", 5 ) } } else { Logging( "Username and password must be part of the ApexURL for ActiveControl to work.", 5 ) } } // OutletMode controls a specific outlet def OutletMode( String DID, String Value ){ if( ActiveControl ){ if( state.Cookie == null ){ GetCookie() pauseExecution( 2000 ) } def Params = [ uri: "${ ApexURL }/rest/status/outputs/${ DID }", contentType: "application/json", headers: [ Cookie: "${ state.Cookie }" ], body:"{\"status\":[\"${ Value }\",\"\",\"OK\",\"\"],\"gid\":\"\",\"type\":\"outlet\",\"did\":\"${ DID }\"}" ] asynchttpPut( "ReceiveActiveControlData", Params, [ Method: "OutletMode", DID: "${ DID }", Value: "${ Value }" ] ) } else { Logging( "ActiveControl not enabled, cannot change outlet ${ DID } to ${ Value }", 4 ) } } // RunFeedCycle performs one of the pre-configured feeding cycles def RunFeedCycle( String FeedCycle ){ if( ActiveControl ){ if( state.Cookie == null ){ GetCookie() pauseExecution( 2000 ) } def CycleNumber if( FeedCycle == "A" ){ CycleNumber = 1 } else if( FeedCycle == "B" ){ CycleNumber = 2 } else if( FeedCycle == "C" ){ CycleNumber = 3 } else if( FeedCycle == "D" ){ CycleNumber = 4 } else if( FeedCycle == "Cancel" ){ CycleNumber = 0 } def Params = [ uri: "${ ApexURL }/rest/status/feed/${ CycleNumber }", contentType: "application/json", headers: [ Cookie: "${ state.Cookie }" ], body:"{\"name\":${ CycleNumber },\"active\":1}" ] //Logging( "Params ${ Params }", 4 ) asynchttpPut( "ReceiveActiveControlData", Params, [ Method: "FeedCycle", Value: "${ FeedCycle }" ] ) } else { Logging( "ActiveControl not enabled, cannot run feeding ${ FeedCycle }", 4 ) } } // Handles getting the response from an ActiveControl attempt def ReceiveActiveControlData( resp, data ){ switch( resp.getStatus() ){ case 200: Json = parseJson( resp.data ) Logging( "${ data.Method }= ${ resp.data }", 4 ) def TempDID def Device switch( data.Method ){ case "OutletMode": TempDID = Json.did.split( "_" ) Device = "${ state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type } #${ TempDID[ 0 ] }" if( TempDID[ 1 ] == "1" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "FMM" ) ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } 24v", "On - Auto" ) PostEventToChild( "${ Device }", "24v", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } 24v", "On" ) PostEventToChild( "${ Device }", "24v", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } 24v", "Off - Auto" ) PostEventToChild( "${ Device }", "24v", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } 24v", "Off" ) PostEventToChild( "${ Device }", "24v", "Off" ) } } else if( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "Virtual" ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } ${ TempDID[ 1 ] }", "On - Auto" ) PostEventToChild( "${ Device }", "Virtual ${ TempDID[ 1 ] } Switch", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } ${ TempDID[ 1 ] }", "On" ) PostEventToChild( "${ Device }", "Virtual ${ TempDID[ 1 ] } Switch", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } ${ TempDID[ 1 ] }", "Off - Auto" ) PostEventToChild( "${ Device }", "Virtual ${ TempDID[ 1 ] } Switch", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } ${ TempDID[ 1 ] }", "Off" ) PostEventToChild( "${ Device }", "Virtual ${ TempDID[ 1 ] } Switch", "Off" ) } } else if( TempDID[ 1 ] == "1" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "Sicce" ) ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } switch", "On - Auto" ) PostEventToChild( "${ Device }", "switch", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } switch", "On" ) PostEventToChild( "${ Device }", "switch", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } switch", "Off - Auto" ) PostEventToChild( "${ Device }", "switch", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } switch", "Off" ) PostEventToChild( "${ Device }", "switch", "Off" ) } else if( Json.status[ 0 ] == "TBL" ){ ProcessState( "${ Device } level", Json.intensity as int ) PostEventToChild( "${ Device }", "level", Json.intensity as int ) } } else if( TempDID[ 1 ] == "1" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "COR" ) ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } switch", "On - Auto" ) PostEventToChild( "${ Device }", "switch", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } switch", "On" ) PostEventToChild( "${ Device }", "switch", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } switch", "Off - Auto" ) PostEventToChild( "${ Device }", "switch", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } switch", "Off" ) PostEventToChild( "${ Device }", "switch", "Off" ) } else if( Json.status[ 0 ] == "TBL" ){ ProcessState( "${ Device } level", Json.intensity as int ) PostEventToChild( "${ Device }", "level", Json.intensity as int ) } } else if( TempDID[ 1 ] == "1" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "WAV" ) ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } switch", "On - Auto" ) PostEventToChild( "${ Device }", "switch", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } switch", "On" ) PostEventToChild( "${ Device }", "switch", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } switch", "Off - Auto" ) PostEventToChild( "${ Device }", "switch", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } switch", "Off" ) PostEventToChild( "${ Device }", "switch", "Off" ) } else if( Json.status[ 0 ] == "TBL" ){ ProcessState( "${ Device } level", Json.intensity as int ) PostEventToChild( "${ Device }", "level", Json.intensity as int ) } } else if( TempDID[ 1 ] == "1" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "SKY" ) ){ ProcessState( "${ Device } Sky Intensity", Json.intensity as int ) PostEventToChild( "${ Device }", "Sky Intensity", Json.intensity as int ) } else if( TempDID[ 1 ] == "2" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "SKY" ) ){ ProcessState( "${ Device } Moon Intensity", Json.intensity as int ) PostEventToChild( "${ Device }", "Moon Intensity", Json.intensity as int ) } else if( TempDID[ 1 ] == "1" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "AFS" ) ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } Feeder Mode", "On - Auto" ) PostEventToChild( "${ Device }", "Feeder Mode", "On - Auto" ) PostEventToChild( "${ Device }", "switch", "on" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } Feeder Mode", "On" ) PostEventToChild( "${ Device }", "Feeder Mode", "On" ) PostEventToChild( "${ Device }", "switch", "on" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } Feeder Mode", "Off - Auto" ) PostEventToChild( "${ Device }", "Feeder Mode", "Off - Auto" ) PostEventToChild( "${ Device }", "switch", "off" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } Feeder Mode", "Off" ) PostEventToChild( "${ Device }", "Feeder Mode", "Off" ) PostEventToChild( "${ Device }", "switch", "off" ) } } else if( TempDID[ 1 ] == "3" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "Trident" ) ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } CA/MG Test Setting", "On - Auto" ) PostEventToChild( "${ Device }", "CA/MG Test Setting", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } CA/MG Test Setting", "On" ) PostEventToChild( "${ Device }", "CA/MG Test Setting", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } CA/MG Test Setting", "Off - Auto" ) PostEventToChild( "${ Device }", "CA/MG Test Setting", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } CA/MG Test Setting", "Off" ) PostEventToChild( "${ Device }", "CA/MG Test Setting", "Off" ) } } else if( TempDID[ 1 ] == "4" && ( state.'Module Map'.find{ it.DID == TempDID[ 0 ] }.Type == "Trident" ) ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } ALK Test Setting", "On - Auto" ) PostEventToChild( "${ Device }", "ALK Test Setting", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } ALK Test Setting", "On" ) PostEventToChild( "${ Device }", "ALK Test Setting", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } ALK Test Setting", "Off - Auto" ) PostEventToChild( "${ Device }", "ALK Test Setting", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } ALK Test Setting", "Off" ) PostEventToChild( "${ Device }", "ALK Test Setting", "Off" ) } } else if( TempDID[ 1 ] == "9" ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } 24v A", "On - Auto" ) PostEventToChild( "${ Device }", "24v A", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } 24v A", "On" ) PostEventToChild( "${ Device }", "24v A", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } 24v A", "Off - Auto" ) PostEventToChild( "${ Device }", "24v A", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } 24v A", "Off" ) PostEventToChild( "${ Device }", "24v A", "Off" ) } } else if( TempDID[ 1 ] == "10" ){ if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } 24v B", "On - Auto" ) PostEventToChild( "${ Device }", "24v B", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } 24v B", "On" ) PostEventToChild( "${ Device }", "24v B", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } 24v B", "Off - Auto" ) PostEventToChild( "${ Device }", "24v B", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } 24v B", "Off" ) PostEventToChild( "${ Device }", "24v B", "Off" ) } } else { // All the non-special case outlets if( Json.status[ 0 ] == "AON" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "On - Auto" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "On - Auto" ) } else if( Json.status[ 0 ] == "ON" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "On" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "On" ) } else if( Json.status[ 0 ] == "AOF" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "Off - Auto" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "Off - Auto" ) } else if( Json.status[ 0 ] == "OFF" ){ ProcessState( "${ Device } Outlet ${ TempDID[ 1 ] }", "Off" ) PostEventToChild( "${ Device }", "Outlet ${ TempDID[ 1 ] }", "Off" ) } else if( Json.status[ 0 ] == "AUTO" ){ refresh() } } break case "FeedCycle": //Feeding cycle activated if( Json.active == 1 ){ Logging( "Feed Cycle ${ data.Value } activated", 2 ) } else { Logging( "Feed Cycle ${ data.Value } did not activate", 2 ) } break case "Status": ProcessAPIStatus( resp.data ) break default: Logging( "Unknown method: ${ data.Method }= ${ resp.data }", 3 ) break } break case 401: Logging( "${ data.Method } Unauthorized, probably cookie failure. Use GetCookie command then try again.", 5 ) break default: Logging( "Problem with: ${ data.Method }: ${ resp.getStatus() }", 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() } // uninstalling device so make sure to clean up children void uninstalled() { // Delete all children getChildDevices().each{ deleteChildDevice( it.deviceNetworkId ) } Logging( "Uninstalled", 2 ) } // Used to convert epoch values to text dates def String ConvertEpochToDate( Number Epoch ){ def date = use( groovy.time.TimeCategory ) { new Date( 0 ) + Epoch.seconds } return date } // Checks the location.getTemperatureScale() to convert temperature values def ConvertTemperature( String Scale, Number Value ){ if( Value != null ){ def ReturnValue = Value as float if( location.getTemperatureScale() == "C" && Scale.toUpperCase() == "F" ){ ReturnValue = ( ( ( Value - 32 ) * 5 ) / 9 ) Logging( "Temperature Conversion ${ Value }°${ Scale.toUpperCase() } to ${ ReturnValue }°${ location.getTemperatureScale() }", 4 ) } else if( location.getTemperatureScale() == "F" && Scale.toUpperCase() == "C" ) { ReturnValue = ( ( ( Value * 9 ) / 5 ) + 32 ) Logging( "Temperature Conversion ${ Value }°${ Scale.toUpperCase() } to ${ ReturnValue }°${ location.getTemperatureScale() }", 4 ) } else if( location.getTemperatureScale() == Scale.toUpperCase() ){ ReturnValue = Value } return ReturnValue } } // 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 }" ) } } def GetChildType( Value ){ def ChildType = "" if( ActiveControl ){ switch( Value ){ case "ApexClassic": ChildType = "ApexClassic" break case "ApexJr": ChildType = "ApexJr" break case "Apex2016": ChildType = "Apex2016" break case "ApexEL": ChildType = "ApexEL" break case "ApexA3": ChildType = "ApexA3" break case "ApexA3Pro": ChildType = "ApexA3Pro" break case "ApexA3Jr": ChildType = "ApexA3Jr" break case "EB4": ChildType = "EB4" break case "EB8": ChildType = "EB8" break case "EB832": ChildType = "EB832" break case "PM1": ChildType = "PM1" break case "PM2": ChildType = "PM2" break case "PM3": ChildType = "PM3" break case "ASM": ChildType = "ASM" break case "Trident": ChildType = "Trident" break case "AFS": ChildType = "AFS" break case "FMM": // ATK, FMK, and LDK cannot be identified separately from an FMM because they are kits // that use the FMM module ChildType = "FMM" break case "VDM": ChildType = "VDM" break case "AI": ChildType = "AI" break case "Vortech": ChildType = "Vortech" break case "DOS": ChildType = "DOS" break case "Virtual": ChildType = "Virtual" break case "Sicce": ChildType = "Sicce" break case "COR": ChildType = "COR" break case "SKY": ChildType = "SKY" break case "WAV": ChildType = "WAV" break case "LSM": // Cannot yet be identified, please send me a JSON if you have one case "WXM": // Cannot yet be identified, please send me a JSON if you have one case "Unknown": // Cases where I do not even know of the module type default: ChildType = "Generic" break } } else { ChildType = "Generic" } return ChildType } // Post data to child device def PostEventToChild( Child, Variable, Value, Unit = null ){ if( ChildrenEnabled ){ if( "${ Child }" != null ){ if( getChildDevice( "${ Child }" ) == null ){ addChild( "${ Child }", GetChildType( Child.split( " " )[ 0 ] ) ) } if( getChildDevice( "${ Child }" ) != null ){ if( Unit != null ){ getChildDevice( "${ Child }" ).ProcessEvent( "${ Variable }", Value, "${ Unit }" ) } else { getChildDevice( "${ Child }" ).ProcessEvent( "${ Variable }", Value ) } } else { if( Unit != null ){ Logging( "Failure to add ${ Child } and post ${ Variable }=${ Value }${ Unit }", 5 ) } else { Logging( "Failure to add ${ Child } and post ${ Variable }=${ Value }", 5 ) } } } else { Logging( "Failure to add child because child name was null", 5 ) } } } // Post data to child device def PostStateToChild( Child, Variable, Value ){ if( ChildrenEnabled ){ if( "${ Child }" != null ){ if( getChildDevice( "${ Child }" ) == null ){ addChild( "${ Child }", GetChildType( Child.split( " " )[ 0 ] ) ) } if( getChildDevice( "${ Child }" ) != null ){ getChildDevice( "${ Child }" ).ProcessState( "${ Variable }", Value ) } else { Logging( "Failure to add ${ Child } and post ${ Variable }=${ Value }", 5 ) } } else { Logging( "Failure to add child because child name was null", 5 ) } } } // Based on @mircolino's method for child sensors def addChild( String DNI, String ChildType = null ){ if( ChildrenEnabled ){ try{ Logging( "addChild(${ DNI })", 3 ) if( ActiveControl ){ switch( ChildType ){ case "ApexClassic": addChildDevice( "NSChild-ApexClassic", DNI, [ name: "${ DNI }" ] ) break case "ApexJr": addChildDevice( "NSChild-ApexJr", DNI, [ name: "${ DNI }" ] ) break case "Apex2016": addChildDevice( "NSChild-Apex2016", DNI, [ name: "${ DNI }" ] ) break case "ApexEL": addChildDevice( "NSChild-ApexEL", DNI, [ name: "${ DNI }" ] ) break case "ApexA3": addChildDevice( "NSChild-ApexA3", DNI, [ name: "${ DNI }" ] ) break case "ApexA3Pro": addChildDevice( "NSChild-ApexA3Pro", DNI, [ name: "${ DNI }" ] ) break case "ApexA3Jr": addChildDevice( "NSChild-ApexA3Jr", DNI, [ name: "${ DNI }" ] ) break case "EB4": addChildDevice( "NSChild-EB4", DNI, [ name: "${ DNI }" ] ) break case "EB8": addChildDevice( "NSChild-EB8", DNI, [ name: "${ DNI }" ] ) break case "EB832": addChildDevice( "NSChild-EB832", DNI, [ name: "${ DNI }" ] ) break case "PM1": addChildDevice( "NSChild-PM1", DNI, [ name: "${ DNI }" ] ) break case "PM2": addChildDevice( "NSChild-PM2", DNI, [ name: "${ DNI }" ] ) break case "PM3": addChildDevice( "NSChild-PM3", DNI, [ name: "${ DNI }" ] ) break case "ASM": addChildDevice( "NSChild-ASM", DNI, [ name: "${ DNI }" ] ) break case "Trident": addChildDevice( "NSChild-Trident", DNI, [ name: "${ DNI }" ] ) break case "AFS": addChildDevice( "NSChild-AFS", DNI, [ name: "${ DNI }" ] ) break case "FMM": // ATK, FMK, and LDK cannot be identified separately from an FMM because they are kits // that use the FMM module addChildDevice( "NSChild-FMM", DNI, [ name: "${ DNI }" ] ) break case "VDM": addChildDevice( "NSChild-VDM", DNI, [ name: "${ DNI }" ] ) break case "AI": addChildDevice( "NSChild-AI", DNI, [ name: "${ DNI }" ] ) break case "Radion": addChildDevice( "NSChild-Radion", DNI, [ name: "${ DNI }" ] ) break case "Vortech": addChildDevice( "NSChild-Vortech", DNI, [ name: "${ DNI }" ] ) break case "Vectra": addChildDevice( "NSChild-Vectra", DNI, [ name: "${ DNI }" ] ) break case "DOS": addChildDevice( "NSChild-DOS", DNI, [ name: "${ DNI }" ] ) break case "Virtual": addChildDevice( "NSChild-Virtual", DNI, [ name: "${ DNI }" ] ) break case "Sicce": addChildDevice( "NSChild-Sicce", DNI, [ name: "${ DNI }" ] ) break case "COR": addChildDevice( "NSChild-COR", DNI, [ name: "${ DNI }" ] ) break case "SKY": addChildDevice( "NSChild-SKY", DNI, [ name: "${ DNI }" ] ) break case "WAV": addChildDevice( "NSChild-WAV", DNI, [ name: "${ DNI }" ] ) break case "LSM": // Cannot yet be identified, please send me a JSON if you have one case "WXM": // Cannot yet be identified, please send me a JSON if you have one case "Unknown": // Cases where I do not even know of the module type default: addChildDevice( "NeptuneSystemsApexChild", DNI, [ name: "${ DNI }" ] ) break } } else { addChildDevice( "NeptuneSystemsApexChild", DNI, [ name: "${ DNI }" ] ) } } catch( Exception e ){ def Temp = e as String if( Temp.contains( "not found" ) ){ if( ActiveControl ){ Logging( "NSChild-${ ChildType } driver is not loaded, but is needed for ${ DNI }.", 5 ) } else { Logging( "NeptuneSystemsApexChild driver is not loaded, but is needed for child devices.", 5 ) } } else { Logging( "addChild Error, likely child already exists: ${ Temp }", 5 ) } } } } // 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 } } }