/**
* Automower Device (Hubitat)
*
* 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.
*
* Modified June 18, 2024
*
* lgk 6/24 issue where mower keeps alerting parked_in_cs then charging then repeat and rinse..
* to get around this i will ignore parked_in_cs when last mower activity was charging.
* this is to stop getting numerous alerts
*/
//file:noinspection unused
static String getVersionNum() { return "00.00.06" }
static String getVersionLabel() { return "Husqvarna AutoMower, version ${getVersionNum()}" }
import groovy.transform.Field
import java.text.SimpleDateFormat
@Field static final String sNULL = (String)null
@Field static final String sBLANK = ''
@Field static final String sSPACE = ' '
@Field static final String sCLRRED = 'red'
@Field static final String sCLRGRY = 'gray'
@Field static final String sCLRORG = 'orange'
@Field static final String sLINEBR = '
'
metadata{
definition (
name: "Husqvarna AutoMower",
namespace: "imnotbob",
author: "imnot_bob",
importUrl: "https://raw.githubusercontent.com/imnotbob/AutoMower/master/mower-device.groovy"
)
{
capability "Actuator"
capability "Sensor"
capability "Refresh"
capability "Motion Sensor"
capability "Battery"
capability "Power Source"
attribute 'mowerStatus', 'STRING'
//MAIN_AREA, SECONDARY_AREA, HOME, DEMO, UNKNOWN
attribute 'mowerActivity', 'STRING'
//UNKNOWN, NOT_APPLICABLE, MOWING, GOING_HOME, CHARGING, LEAVING, PARKED_IN_CS, STOPPED_IN_GARDEN
attribute 'mowerState', 'STRING'
//UNKNOWN, NOT_APPLICABLE, PAUSED, IN_OPERATION, WAIT_UPDATING, WAIT_POWER_UP, RESTRICTED,
// OFF, STOPPED, ERROR, FATAL_ERROR, ERROR_AT_POWER_UP
attribute 'mowerConnected', 'STRING' // TRUE or FALSE
attribute 'mowerTimeStamp', 'STRING' // LAST TIME connected (EPOCH LONG)
//attribute 'battery' 'NUMBER' // Battery %
attribute 'errorCode', 'STRING' // current error code
attribute 'errorCodeS', 'STRING' // current error code
attribute 'errorTimeStamp', 'NUMBER' // (EPOCH LONG)
attribute 'plannerNextStart', 'NUMBER' // (EPOCH LONG)
attribute 'plannerOverride', 'STRING' // Override Action
attribute 'name', 'STRING'
attribute 'model', 'STRING'
attribute 'serialNumber', 'STRING'
attribute 'cuttingHeight', 'NUMBER' // (level)
attribute 'headlight', 'STRING' // ALWAYS_ON, ALWAYS_OFF, EVENING_ONLY, EVENING_AND_NIGHT
attribute 'apiConnected', 'STRING'
attribute 'debugEventFromParent', 'STRING' // Read only
attribute 'debugLevel', 'NUMBER' // Read only - changed in preferences
attribute 'lastPoll', 'STRING'
// deductions
//attribute 'motion' 'ENUM' // active, inactive
//attribute 'powerSource' 'ENUM' // "battery", "dc", "mains", "unknown"
attribute 'stuck', 'STRING' // TRUE or FALSE
attribute 'parked', 'STRING' // TRUE or FALSE
attribute 'hold', 'STRING' // TRUE or FALSE
attribute 'holdUntilNext', 'STRING' // TRUE or FALSE
attribute 'holdIndefinite', 'STRING' // TRUE or FALSE
attribute 'cuttingBladeUsageTime', 'NUMBER'
attribute 'numberOfChargingCycles', 'NUMBER'
attribute 'numberOfCollisions', 'NUMBER'
attribute 'totalChargingTime', 'NUMBER'
attribute 'totalCuttingTime', 'NUMBER'
attribute 'totalRunningTime', 'NUMBER'
attribute 'totalSearchingTime', 'NUMBER'
// attribute 'longitude', 'NUMBER'
// attribute 'latitude', 'NUMBER'
// attribute 'lastUpdate', 'String'
// attribute 'nextRun', 'String'
command "start", [[name: 'Duration*', type: 'NUMBER', description: 'Minutes']] // duration
command "pause", []
command "parkuntilnext", [] // until next schedule
command "parkindefinite", [] // park until further notice
command "park", [[name: 'Duration*', type: 'NUMBER', description: 'Minutes']] // duration in minutes
command "resumeSchedule", []
command "setCuttingHeight", [[name: 'Height*', type: 'NUMBER', description: 'Level']]
command "setHeadlightMode", [[name: 'Mode*', type: 'ENUM', description: 'Mode', constraints: ["ALWAYS_ON", "ALWAYS_OFF", "EVENING_ONLY", "EVENING_AND_NIGHT"]]] // mode
command "setSchedule", [[name: 'taskList*', type: 'STRING', description: 'Task List']]
}
preferences{
input(name: "dummy", type: "text", title: "${getVersionLabel()}", description: " ", required: false)
}
}
// parse events into attributes
def parse(String description){
LOG("parse() --> Parsing ${description}", 4, sTRACE)
}
def refresh(Boolean force=false){
// No longer require forcePoll on every refresh - just get whatever has changed
LOG("refresh() - calling pollChildren ${force?(force):sBLANK}, deviceId= ${getDeviceId()}",2,sINFO)
parent.pollFromChild(getDeviceId(), force) // tell parent to just poll me silently -- can't pass child/this for some reason
}
void doRefresh(){
// Pressing refresh within 6 seconds of the prior refresh completing will force a complete poll - otherwise changes only
refresh(state.lastDoRefresh?((wnow()-state.lastDoRefresh)<6000):false)
state.lastDoRefresh= wnow() // reset the timer after the UI has been updated
}
def forceRefresh(){
refresh(true)
}
def installed(){
LOG("${device.label} being installed",2,sINFO)
if(device.label?.contains('TestingForInstall')) return // we're just going to be deleted in a second...
updated()
}
def uninstalled(){
LOG("${device.label} being uninstalled",2,sINFO)
}
def updated(){
LOG("${getVersionLabel()} updated",1,sTRACE)
if(device.displayName.contains('TestingForInstall')){ return }
state.remove('LastLOGerrorDate')
state.remove('lastLOGerror')
device.deleteCurrentState('debugEventFromParent')
state.version= getVersionLabel()
updateDataValue("myVersion", getVersionLabel())
runIn(2, 'forceRefresh', [overwrite: true])
}
def poll(){
LOG("Executing 'poll' using parent App", 2, sINFO)
parent.pollFromChild(getDeviceId(), false) // tell parent to just poll me silently -- can't pass child/this for some reason
}
def generateEvent(Map updates){
//log.error "generateEvent(Map): ${updates}"
generateEvent([updates])
}
def generateEvent(List