Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Beta webCoRE for HE
/*
* webCoRE - Community's own Rule Engine - Web Edition
*
* Copyright 2016 Adrian Caramaliu <ady624("at" sign goes here)gmail.com>
*
* webCoRE Piston
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Last update January 28, 2021 for Hubitat
*/
static String version(){ return 'v0.3.110.20191009' }
static String HEversion(){ return 'v0.3.110.20210123_HE' }
/** webCoRE DEFINITION **/
static String handle(){ return 'webCoRE' }
import groovy.json.*
import hubitat.helper.RMUtils
import groovy.transform.Field
definition(
name:handle()+' Piston',
namespace:'ady624',
author:'Adrian Caramaliu',
description:'Do not install this directly, use webCoRE instead',
category:'Convenience',
parent:'ady624:'+handle(),
iconUrl:'https://raw.githubusercontent.com/ady624/webCoRE/master/resources/icons/app-CoRE.png',
iconX2Url:'https://raw.githubusercontent.com/ady624/webCoRE/master/resources/icons/app-CoRE@2x.png',
iconX3Url:'https://raw.githubusercontent.com/ady624/webCoRE/master/resources/icons/app-CoRE@3x.png',
importUrl:'https://raw.githubusercontent.com/imnotbob/webCoRE/hubitat-patches/smartapps/ady624/webcore-piston.src/webcore-piston.groovy'
)
preferences{
page(name:'pageMain')
page(name:'pageRun')
page(name:'pageClear')
page(name:'pageClearAll')
page(name:'pageDumpPiston')
page(name:'pageDumpPiston1')
}
static Boolean eric(){ return true }
static Boolean eric1(){ return true }
@Field static final String sNULL=(String)null
@Field static final String sSNULL='null'
@Field static final String sBOOLN='boolean'
@Field static final String sLONG='long'
@Field static final String sSTR='string'
@Field static final String sINT='integer'
@Field static final String sDCML='decimal'
@Field static final String sDYN='dynamic'
@Field static final String sDTIME='datetime'
@Field static final String sTRUE='true'
@Field static final String sFALSE='false'
@Field static final String sTIME='time'
@Field static final String sDATE='date'
@Field static final String sDEV='device'
@Field static final String sDBL='double'
@Field static final String sNUMBER='number'
@Field static final String sFLOAT='float'
@Field static final String sVARIABLE='variable'
@Field static final String sERROR='error'
@Field static final String sON='on'
@Field static final String sOFF='off'
@Field static final String sSWITCH='switch'
@Field static final String sTRIG='trigger'
@Field static final String sCONDITION='condition'
@Field static final String sDURATION='duration'
@Field static final String sDLLRINDX='$index'
@Field static final String sDLLRDEVS='$devices'
@Field static final String sDLLRDEVICE='$device'
@Field static final String sTEXT='text'
@Field static final String sENUM='enum'
@Field static final String sTHREAX='threeAxis'
@Field static final String sBLK=''
@Field static final String sCOMMA=','
@Field static final String sSPC=' '
@Field static final String sV='v'
@Field static final String sP='p'
@Field static final String sS='s'
@Field static final String sC='c'
@Field static final String sH='h'
@Field static final String sR='r'
@Field static final String sB='b'
@Field static final String sI='i'
@Field static final String sT='t'
@Field static final String sE='e'
@Field static final String sD='d'
@Field static final String sX='x'
@Field static final String sLB='['
@Field static final String sRB=']'
@Field static final String sAT='@'
@Field static final String sDLR='$'
@Field static final String sPEVDATE='$previousEventDate'
@Field static final String sPEVDELAY='$previousEventDelay'
@Field static final String sPEVDEV='$previousEventDevice'
@Field static final String sPEVDEVINDX='$previousEventDeviceIndex'
@Field static final String sPEVATTR='$previousEventAttribute'
@Field static final String sPEVDESC='$previousEventDescription'
@Field static final String sPEVVALUE='$previousEventValue'
@Field static final String sPEVUNIT='$previousEventUnit'
@Field static final String sPEVPHYS='$previousEventDevicePhysical'
@Field static final String sCURDATE='$currentEventDate'
@Field static final String sCURDELAY='$currentEventDelay'
@Field static final String sCURDEV='$currentEventDevice'
@Field static final String sCURDEVINDX='$currentEventDeviceIndex'
@Field static final String sCURATTR='$currentEventAttribute'
@Field static final String sCURDESC='$currentEventDescription'
@Field static final String sCURVALUE='$currentEventValue'
@Field static final String sCURUNIT='$currentEventUnit'
@Field static final String sCURPHYS='$currentEventDevicePhysical'
@Field static final String sAPPJSON='application/json'
@Field static final String sASYNCREP='wc_async_reply'
@Field static final String sLVL='level'
@Field static final String sSTLVL='setLevel'
@Field static final String sIFLVL='infraredLevel'
@Field static final String sSTIFLVL='setInfraredLevel'
@Field static final String sSATUR='saturation'
@Field static final String sSSATUR='setSaturation'
@Field static final String sHUE='hue'
@Field static final String sSHUE='setHue'
@Field static final String sSCLR='setColor'
@Field static final String sCLRTEMP='colorTemperature'
@Field static final String sSCLRTEMP='setColorTemperature'
@Field static final String sZEROS='000000'
@Field static final String sHTTPR='httpRequest'
@Field static final String sSENDE='sendEmail'
@Field static final String sANY='any'
@Field static final String sALL='all'
@Field static final String sAND='and'
@Field static final String sOR='or'
@Field static final String sIF='if'
@Field static final String sWHILE='while'
@Field static final String sREPEAT='repeat'
@Field static final String sFOR='for'
@Field static final String sEACH='each'
@Field static final String sACTION='action'
@Field static final String sEVERY='every'
@Field static final String sRESTRIC='restriction'
@Field static final String sGROUP='group'
@Field static final String sDO='do'
@Field static final String sEVENT='event'
@Field static final String sEXIT='exit'
@Field static final String sBREAK='break'
@Field static final String sEXPR='expression'
@Field static final String sOPER='operator'
@Field static final String sOPERAND='operand'
@Field static final String sFUNC='function'
@Field static final String sONE='1'
@Field static final String sPLUS='+'
@Field static final String sMINUS='-'
@Field static final String sDOT='.'
@Field static final String sORIENT='orientation'
@Field static final String sAXISX='axisX'
@Field static final String sAXISY='axisY'
@Field static final String sAXISZ='axisZ'
@Field static final String sEXPECTING='Expecting '
@Field static final String sINT32='int32'
@Field static final String sINT64='int64'
@Field static final String sBOOL='bool'
@Field static final String sPHONE='phone'
@Field static final String sURI='uri'
@Field static final String sSTOREM='storeMedia'
@Field static final String sIFTTM='iftttMaker'
@Field static final String sDOLARGS='$args'
@Field static final String sEND='end'
@Field static final String sHTTPCONTENT='$httpContentType'
@Field static final String sHTTPSTSCODE='$httpStatusCode'
@Field static final String sHTTPSTSOK='$httpStatusOk'
@Field static final String sIFTTTSTSCODE='$iftttStatusCode'
@Field static final String sIFTTTSTSOK='$iftttStatusOk'
@Field static final String sTSLF='theSerialLockFLD'
@Field static final String sTCCC='theCCC'
@Field static final String sTCL='cacheLock'
@Field static final String sTGBL='theGlobal'
@Field static final String sLCK1='lockOrQueue1'
@Field static final String sLCK2='lockOrQueue2'
@Field static final String sGETTRTD='getTempRtd'
@Field static final String sHNDLEVT='handleEvent'
@Field static final String sVALUEN='(value1, value2, ..., valueN)'
@Field static final String sMULP='*'
@Field static final String sQM='?'
@Field static final String sCOLON=':'
@Field static final String sPWR='**'
@Field static final String sAMP='&'
@Field static final String sBOR='|'
@Field static final String sBXOR='^'
@Field static final String sBNOT='~'
@Field static final String sBNAND='~&'
@Field static final String sBNOR='~|'
@Field static final String sBNXOR='~^'
@Field static final String sLTH='<'
@Field static final String sGTH='>'
@Field static final String sLTHE='<='
@Field static final String sGTHE='>='
@Field static final String sEQ='=='
@Field static final String sNEQ='!='
@Field static final String sNEQA='<>'
@Field static final String sMOD='%'
@Field static final String sMOD1='\\'
@Field static final String sSBL='<<'
@Field static final String sSBR='>>'
@Field static final String sNEG='!'
@Field static final String sDNEG='!!'
@Field static final String sDIV='/'
@Field static final String sLAND='&&'
@Field static final String sLNAND='!&'
@Field static final String sLOR='||'
@Field static final String sLNOR='!|'
@Field static final String sLXOR='^^'
@Field static final String sLNXOR='!^'
/** CONFIGURATION PAGES **/
def pageMain(){
return dynamicPage(name:'pageMain', title:'', install:true, uninstall: (Integer)state.build!=null){
if(parent==null || !(Boolean)parent.isInstalled()){
section(){
paragraph 'Sorry you cannot install a piston directly from the HE console; please use the webCoRE dashboard (dashboard.webcore.co) instead.'
}
section(sectionTitleStr('Installing webCoRE')){
paragraph 'If you are trying to install webCoRE please go back one step and choose webCoRE, not webCoRE Piston. You can also visit wiki.webcore.co for more information on how to install and use webCoRE'
if(parent!=null){
String t0=(String)parent.getWikiUrl()
href '', title:imgTitle('https://raw.githubusercontent.com/ady624/webCoRE/master/resources/icons/app-CoRE.png', inputTitleStr('More information')), description:t0, style:'external', url:t0, required:false
}
}
}else{
section(sectionTitleStr('General')){
label name:'name', title:'Name', required:true, state:(name ? 'complete':sNULL), defaultValue:(String)parent.generatePistonName(), submitOnChange:true
}
section(sectionTitleStr('Dashboard')){
String dashboardUrl=(String)parent.getDashboardUrl()
if(dashboardUrl!=sNULL){
dashboardUrl=dashboardUrl+'piston/'+hashId(app.id)
href '', title:imgTitle('https://raw.githubusercontent.com/ady624/webCoRE/master/resources/icons/dashboard.png', inputTitleStr('View piston in dashboard')), style:'external', url:dashboardUrl, required:false
}else paragraph 'Sorry your webCoRE dashboard does not seem to be enabled; please go to the parent app and enable the dashboard if needed.'
}
section(sectionTitleStr('Application Info')){
LinkedHashMap<String,Object> rtD=getTemporaryRunTimeData(now())
if(!(Boolean)rtD.enabled)paragraph 'Piston is disabled by webCoRE'
if(!(Boolean)rtD.active)paragraph 'Piston is paused'
if((String)rtD.bin!=sNULL){
paragraph 'Automatic backup bin code: '+(String)rtD.bin
}
paragraph 'Version: '+version()
paragraph 'VersionH: '+HEversion()
paragraph 'Memory Usage: '+mem()
paragraph 'RunTime History: '+runTimeHis(rtD)
rtD=null
}
section(sectionTitleStr('Recovery')){
href 'pageRun', title:'Test run this piston'
href 'pageClear', title:'Clear logs', description:'This will remove logs but no variables'
href 'pageClearAll', title:'Clear all data', description:'This will reset all data stored in local variables'
}
section(){
input 'dev', "capability.*", title:'Devices', description:'Piston devices', multiple:true
input 'logging', "enum", title:'Logging Level', options:[0:"None", 1:"Minimal", 2:"Medium", 3:"Full"], description:'Piston logging', defaultValue:state.logging? state.logging.toString() : '0'
input 'logsToHE', "bool", title:'Piston logs are also displayed in HE console logs?', description:"Logs are available in webCoRE console; also display in HE console 'Logs'?", defaultValue:false
input 'maxStats', "number", title:'Max number of timing history stats', description:'Max number of stats', range: '2..300', defaultValue:50
input 'maxLogs', "number", title:'Max number of history logs', description:'Max number of logs', range: '0..300', defaultValue:50
}
if(eric() || settings.logging?.toInteger()>2){
section('Debug'){
href 'pageDumpPiston', title:'Dump piston structure', description:''
href 'pageDumpPiston1', title:'Dump cached piston structure', description:''
}
}
}
}
}
def pageRun(){
test()
return dynamicPage(name:'pageRun', title:'', uninstall:false){
section('Run'){
paragraph 'Piston tested'
Map t0=(Map)parent.getWCendpoints()
String t1="/execute/${hashId(app.id)}?access_token=${t0.at}".toString()
paragraph "Cloud Execute endpoint ${t0.ep}${t1}".toString()
paragraph "Local Execute endpoint ${t0.epl}${t1}".toString()
}
}
}
private static String sectionTitleStr(String title) { return '<h3>'+title+'</h3>' }
private static String inputTitleStr(String title) { return '<u>'+title+'</u>' }
//private static String pageTitleStr(String title) { return '<h1>'+title+'</h1>' }
//private static String paraTitleStr(String title) { return '<b>'+title+'</b>' }
private static String imgTitle(String imgSrc, String titleStr, String color=sNULL, Integer imgWidth=30, Integer imgHeight=0){
String imgStyle=sBLK
imgStyle += imgWidth>0 ? 'width: '+imgWidth.toString()+'px !important;':sBLK
imgStyle += imgHeight>0 ? imgWidth!=0 ? sSPC:sBLK+'height: '+imgHeight.toString()+'px !important;':sBLK
if(color!=sNULL){ return """<div style="color: ${color}; font-weight: bold;"><img style="${imgStyle}" src="${imgSrc}"> ${titleStr}</img></div>""".toString() }
else{ return """<img style="${imgStyle}" src="${imgSrc}"> ${titleStr}</img>""".toString() }
}
def pageClear(){
clear1(false,true,true,false)
return dynamicPage(name:'pageClear', title:sBLK, uninstall:false){
section('Clear'){
paragraph 'All non-essential data has been cleared.'
}
}
}
void clear1(Boolean ccache=false, Boolean some=true, Boolean most=false, Boolean all=false,Boolean reset=false){
String meth='clear1'
if(some)state.logs=[]
if(most){ state.trace=[:];state.stats=[:] }
if(reset){app.clearSetting('maxLogs'); app.clearSetting('maxStats')}
if(all){
meth +=' all'
LinkedHashMap<String,Object> tRtData=getTemporaryRunTimeData(now())
Boolean act=(Boolean)tRtData.active
Boolean dis=!(Boolean)tRtData.enabled
tRtData=null
state.cache=[:]
state.vars=[:]
state.store=[:]
state.pauses=0L
clearMyCache(meth)
String semaName=app.id.toString()
theSemaphoresFLD[semaName]=0L
theSemaphoresFLD=theSemaphoresFLD
theQueuesFLD[semaName]=[]
theQueuesFLD=theQueuesFLD // this forces volatile cache flush
if(act && !dis){
tRtData=getTemporaryRunTimeData(now())
LinkedHashMap rtD=getRunTimeData(tRtData, null, true, true) //reinitializes cache variables; caches piston
rtD=null
tRtData=null
}
}
clearMyCache(meth)
if(ccache){
clearMyPiston(meth)
}
}
def pageClearAll(){
clear1(true,true,true,true)
return dynamicPage(name:'pageClearAll', title:sBLK, uninstall:false){
section('Clear All'){
paragraph 'All local data has been cleared.'
}
}
}
static String dumpListDesc(data, Integer level, List<Boolean> lastLevel, String listLabel, Boolean html=false){
String str=sBLK
Integer cnt=1
List<Boolean> newLevel=lastLevel
List list1=data?.collect{it}
Integer sz=(Integer)list1.size()
list1?.each{ par ->
Integer t0=cnt-1
String myStr="${listLabel}[${t0}]".toString()
if(par instanceof Map){
Map newmap=[:]
newmap[myStr]=(Map)par
Boolean t1= cnt==sz
newLevel[level]=t1
str += dumpMapDesc(newmap, level, newLevel, !t1, html)
}else if(par instanceof List || par instanceof ArrayList){
Map newmap=[:]
newmap[myStr]=par
Boolean t1= cnt==sz
newLevel[level]=t1
str += dumpMapDesc(newmap, level, newLevel, !t1, html)
}else{
String lineStrt='\n'
for(Integer i=0; i<level; i++){
lineStrt += (i+1<level)? (!lastLevel[i] ? '' : ' '):' '
}
lineStrt += (cnt==1 && sz>1)? '┌─ ':(cnt<sz ? '├─ ' : '└─ ')
if(html)str += '<span>'
str += "${lineStrt}${listLabel}[${t0}]: ${par} (${getObjType(par)})".toString()
if(html)str += '</span>'
}
cnt=cnt+1
}
return str
}
static String dumpMapDesc(data, Integer level, List<Boolean> lastLevel, Boolean listCall=false, Boolean html=false){
String str=sBLK
Integer cnt=1
Integer sz=data?.size()
data?.each{ par ->
String lineStrt
List<Boolean> newLevel=lastLevel
Boolean thisIsLast= cnt==sz && !listCall
if(level>0){
newLevel[(level-1)]=thisIsLast
}
Boolean theLast=thisIsLast
if(level==0){
lineStrt='\n\n'
}else{
theLast= theLast && thisIsLast
lineStrt='\n'
for(Integer i=0; i<level; i++){
lineStrt += (i+1<level)? (!newLevel[i] ? '' : ' '):' '
}
lineStrt += ((cnt<sz || listCall) && !thisIsLast) ? '├─ ' : '└─ '
}
String objType=getObjType(par.value)
if(par.value instanceof Map){
if(html)str += '<span>'
str += "${lineStrt}${(String)par.key}: (${objType})".toString()
if(html)str += '</span>'
newLevel[(level+1)]=theLast
str += dumpMapDesc((Map)par.value, level+1, newLevel, false, html)
}
else if(par.value instanceof List || par.value instanceof ArrayList){
if(html)str += '<span>'
str += "${lineStrt}${(String)par.key}: [${objType}]".toString()
if(html)str += '</span>'
newLevel[(level+1)]=theLast
str += dumpListDesc(par.value, level+1, newLevel, sBLK, html)
}
else{
if(html)str += '<span>'
str += "${lineStrt}${(String)par.key}: (${par.value}) (${objType})".toString()
if(html)str += '</span>'
}
cnt=cnt+1
}
return str
}
static String myObj(obj){
if(obj instanceof String){return 'String'}
else if(obj instanceof Map){return 'Map'}
else if(obj instanceof List){return 'List'}
else if(obj instanceof ArrayList){return 'ArrayList'}
else if(obj instanceof Integer){return 'Int'}
else if(obj instanceof BigInteger){return 'BigInt'}
else if(obj instanceof Long){return 'Long'}
else if(obj instanceof Boolean){return 'Bool'}
else if(obj instanceof BigDecimal){return 'BigDec'}
else if(obj instanceof Float){return 'Float'}
else if(obj instanceof Byte){return 'Byte'}
else{ return 'unknown'}
}
static String getObjType(obj){
return "<span style='color:orange'>"+myObj(obj)+"</span>"
}
static String getMapDescStr(data){
String str
List<Boolean> lastLevel=[true]
str=dumpMapDesc(data, 0, lastLevel, false, true)
return str!=sBLK ? str:'No Data was returned'
}
def pageDumpPiston1(){
LinkedHashMap rtD=getRunTimeData()
LinkedHashMap pis=recreatePiston(true, true)
rtD.piston=pis
subscribeAll(rtD, false)
String message=getMapDescStr(rtD.piston)
rtD=null
pis=null
return dynamicPage(name:'pageDumpPiston1', title:sBLK, uninstall:false){
section('Cached Piston dump'){
paragraph message
}
}
}
def pageDumpPiston(){
LinkedHashMap rtD=getRunTimeData()
// LinkedHashMap pis=recreatePiston(false, true)
String message=getMapDescStr(rtD.piston)
rtD=null
return dynamicPage(name:'pageDumpPiston', title:sBLK, uninstall:false){
section('Full Piston dump'){
paragraph message
}
}
}
void installed(){
if(app.id==null)return
state.created=now()
state.modified=now()
state.build=0
state.vars=(Map)state.vars ?: [:]
state.subscriptions=(Map)state.subscriptions ?: [:]
state.logging=0
initialize()
}
void updated(){
unsubscribe()
initialize()
}
void uninstalled(){
if(eric())log.debug 'uninstalled'
if(!atomicState.pistonDeleted) Map a=deletePiston()
}
void initialize(){
svSunTFLD = null
String tt1=(String)settings.logging
Integer tt2=(Integer)state.logging
String tt3=tt2.toString()
if(tt1==sNULL)Map a=setLoggingLevel(tt2 ? tt3:'0', false)
else if(tt1!=tt3)Map a=setLoggingLevel(tt1, false)
if((Boolean)state.active)Map b=resume()
else {
cleanState()
clearMyCache('initialize')
}
}
@Field static final List<String> clST=['hash', 'piston', 'cVersion', 'hVersion', 'disabled', 'logPExec', 'settings', 'svSunT', 'temp', 'debugLevel']
void cleanState(){
//cleanups between releases
for(sph in state.findAll{ (Boolean)((String)it.key).startsWith('sph')})state.remove(sph.key.toString())
for(String foo in clST)state.remove(foo)
}
/** PUBLIC METHODS **/
Boolean isInstalled(){
return (Long)state.created!=null
}
Map get(Boolean minimal=false){ // minimal is backup
LinkedHashMap rtD=getRunTimeData()
Map rVal=[
meta: [
id: (String)rtD.id,
author: (String)rtD.author,
name: (String)rtD.name,
created: (Long)rtD.created,
modified: (Long)rtD.modified,
build: (Integer)rtD.build,
bin: (String)rtD.bin,
active: (Boolean)rtD.active,
category: rtD.category
],
piston: (LinkedHashMap)rtD.piston
]+(minimal ? [:]:[ // use state as getRunTimeData re-initializes these
systemVars: getSystemVariablesAndValues(rtD),
subscriptions: (Map)state.subscriptions,
state: (Map)state.state,
logging: state.logging!=null ? (Integer)state.logging:0,
stats: (Map)state.stats,
logs: (List)state.logs,
trace: (Map)state.trace,
localVars: (Map)state.vars,
memory: mem(),
lastExecuted: (Long)state.lastExecuted,
nextSchedule: (Long)state.nextSchedule,
schedules: (List)state.schedules
])
rtD=null
return rVal
}
Map activity(lastLogTimestamp){
Map t0=getCachedMaps('activity')
if(t0==null)return [:]
List logs=[]+(List)t0.logs
Integer lsz=(Integer)logs.size()
Long llt=lastLogTimestamp!=null && lastLogTimestamp instanceof String && ((String)lastLogTimestamp).isLong()? (Long)((String)lastLogTimestamp).toLong():0L
Integer index=(llt!=0L && lsz>0)? logs.findIndexOf{ it?.t==llt }:0
index=index>0 ? index:(llt!=0L ? 0:lsz)
Map rVal=[
name: (String)t0.name,
state: (Map)t0.state,
logs: index>0 ? logs[0..index-1]:[],
trace: (Map)t0.trace,
localVars: (Map)t0.vars, // not reporting global or system variable changes
memory: (String)t0.mem,
lastExecuted: (Long)t0.lastExecuted,
nextSchedule: (Long)t0.nextSchedule,
schedules: (List)t0.schedules,
systemVars: (Map)t0.cachePersist
]
t0=null
return rVal
}
Map curPState(){
Map t0=getCachedMaps('curPState',true,false)
if(t0==null)return null
Map st=[:] + (Map)t0.state
st.remove('old')
Map rVal=[
a:(Boolean)t0.active,
c:t0.category,
t:(Long)t0.lastExecuted,
n:(Long)t0.nextSchedule,
z:(String)t0.pistonZ,
s: st,
heCached:(Boolean)t0.Cached ?: false
]
t0=null
return rVal
}
Map clearLogs(){
clear1()
return [:]
}
static String decodeEmoji(String value){
return value.replaceAll(/(\:%[0-9A-F]{2}%[0-9A-F]{2}%[0-9A-F]{2}%[0-9A-F]{2}\:)/,{ m -> URLDecoder.decode(m[0].substring(1, 13), 'UTF-8')})
}
@Field static Map<String,Map> thePistonCacheFLD=[:]
private void clearMyPiston(String meth=sNULL){
String pisName=app.id.toString()
if((Integer)pisName.length()==0)return
Boolean cleared=false
Map pData=(Map)thePistonCacheFLD[pisName]
if(pData!=null){
LinkedHashMap t0=(LinkedHashMap)pData.pis
if(t0){
List data=t0.collect{ it.key }
for(item in data)t0.remove((String)item)
thePistonCacheFLD[pisName].pis=null
mb()
cleared=true
}
pData=null
}
if(cleared && eric())log.debug 'clearing my piston-code-cache '+meth
}
private LinkedHashMap recreatePiston(Boolean shorten=false, Boolean useCache=true){
if(shorten && useCache){
String pisName=app.id.toString()
Map pData=(Map)thePistonCacheFLD[pisName]
if(pData==null || pData.cnt==null){
pData=[cnt:0, pis:null]
thePistonCacheFLD[pisName]=pData
mb()
}
//pData.cnt+=1
if(pData.pis!=null)return (LinkedHashMap)(pData.pis+[cached:true])
}
if(eric())log.debug "recreating piston $shorten $useCache"
String sdata=sBLK
Integer i=0
while(true){
String s=(String)settings."chunk:$i"
if(s!=null)sdata += s
else break
i++
}
if(sdata!=sBLK){
def data=(LinkedHashMap)new groovy.json.JsonSlurper().parseText(decodeEmoji(new String(sdata.decodeBase64(), 'UTF-8')))
LinkedHashMap piston=[
o: data.o ?: [:],
r: data.r ?: [],
rn: !!data.rn,
rop: data.rop ?: sAND,
s: data.s ?: [],
v: data.v ?: [],
z: data.z ?: sBLK
]
state.pistonZ=(String)piston.z
clearMsetIds(piston)
Integer a=msetIds(shorten, piston)
return piston
}
return [:]
}
Map setup(LinkedHashMap data, chunks){
if(data==null){
log.error 'setup: no data'
return [:]
}
clearMyCache('setup')
String semaName=app.id.toString()
Boolean aa=getTheLock(semaName, 'setup')
state.modified=now()
state.build=(Integer)state.build!=null ? (Integer)state.build+1:1
LinkedHashMap piston=[
o: data.o ?: [:],
r: data.r ?: [],
rn: !!data.rn,
rop: data.rop ?: sAND,
s: data.s ?: [],
v: data.v ?: [],
z: data.z ?: sBLK
]
String meth='setup'
clearMyPiston(meth)
clearMsetIds(piston)
Integer a=msetIds(false, piston)
for(chunk in settings.findAll{ (Boolean)((String)it.key).startsWith('chunk:') && !chunks[(String)it.key] }){
app.clearSetting((String)chunk.key)
}
for(chunk in chunks)app.updateSetting((String)chunk.key, [type:sTEXT, value:chunk.value])
app.updateSetting('bin', [type:sTEXT, value:(String)state.bin ?: sBLK])
app.updateSetting('author', [type:sTEXT, value:(String)state.author ?: sBLK])
state.pep=piston.o?.pep ? true:false
String lbl=(String)data.n
if(lbl){
state.svLabel=lbl
atomicState.svLabel=lbl
app.updateLabel(lbl)
}
state.schedules=[]
state.vars=(Map)state.vars ?: [:]
state.modifiedVersion=version()
state.cache=[:]
state.logs=[]
state.trace=[:]
Map rtD=[:]
rtD.piston=piston
releaseTheLock(semaName)
if((Integer)state.build==1 || (Boolean)state.active)rtD=resume(piston)
else clearMyCache('setup')
return [active:(Boolean)state.active, build:(Integer)state.build, modified:(Long)state.modified, state:(Map)state.state, rtData:rtD]
}
private void clearMsetIds(node){
if(item==null)return
for(list in node.findAll{ it.value instanceof List }){
for(item in ((List)list.value).findAll{ it instanceof Map })clearMsetIds(item)
}
if(node instanceof Map && node[sDLR]!=null)node.remove(sDLR)
for(item in node.findAll{ it.value instanceof Map })clearMsetIds(item)
}
private Integer msetIds(Boolean shorten, node, Integer maxId=0, Map<String,Integer> existingIds=[:], List<Map> requiringIds=[], Integer level=0){
String nodeT=node?.t
if(nodeT in [sIF, sWHILE, sREPEAT, sFOR, sEACH, sSWITCH, sACTION, sEVERY, sCONDITION, sRESTRIC, sGROUP, sDO, sON, sEVENT, sEXIT, sBREAK]){
Integer id=node[sDLR]!=null ? (Integer)(node[sDLR]):0
if(id==0 || existingIds[id.toString()]!=null){
Boolean a=requiringIds.push(node)
}else{
maxId=maxId<id ? id:maxId
existingIds[id.toString()]=id
}
if(nodeT==sIF && (List<Map>)node.ei){
Boolean a=((List<Map>)node.ei).removeAll{ !it.c && !it.s }
for(Map elseIf in (List<Map>)node.ei){
id=elseIf[sDLR]!=null ? (Integer)elseIf[sDLR]:0
if(id==0 || existingIds[id.toString()]!=null){
Boolean aa=requiringIds.push(elseIf)
}else{
maxId=(maxId<id)? id:maxId
existingIds[id.toString()]=id
}
}
}
if(nodeT==sSWITCH && node.cs){
for(Map _case in (List<Map>)node.cs){
id=_case[sDLR]!=null ? (Integer)_case[sDLR]:0
if(id==0 || existingIds[id.toString()]!=null)Boolean a=requiringIds.push(_case)
else{
maxId=(maxId<id)? id:maxId
existingIds[id.toString()]=id
}
}
}
if(nodeT==sACTION && node.k){
for(Map task in (List<Map>)node.k){
id=task[sDLR]!=null ? (Integer)task[sDLR]:0
if(id==0 || existingIds[id.toString()]!=null)Boolean a=requiringIds.push(task)
else{
maxId=(maxId<id)? id:maxId
existingIds[id.toString()]=id
}
}
}
}
for(list in node.findAll{ it.value instanceof List }){
for(item in ((List)list.value).findAll{ it instanceof Map })maxId=msetIds(shorten, item, maxId, existingIds, requiringIds, level+1)
}
if(level==0){
for(item in requiringIds){
maxId += 1
item[sDLR]=maxId
}
if(shorten)cleanCode(node)
}
return maxId
}
private void cleanCode(item){
if(item==null || !(item instanceof Map))return
if((String)item.t in [sC, sS, sV, sE]){ // operand values that don't need g,a
if((String)item.g=='avg')item.remove('g')
if(item.a instanceof String)item.remove('a')
}
if((String)item.t in [sC, sS, sX, sV, sE]){ // operand values that don't need d
if(item.d instanceof List)item.remove(sD)
}
if((String)item.t in [sS, sX, sV, sE] || ((String)item.t==sC && !((String)item.vt in [sTIME, sDATE, sDTIME])) ){ // operand values that don't need c
item.remove(sC)
}
if(item.t==null && item.size()==4 && item.d instanceof List && !item.d && (String)item.g=='avg' && item.f=='l' && item.vt){
item.remove(sD); item.remove('g'); item.remove('f')
}
if(item.str!=null)item.remove('str')
if(item.ok!=null)item.remove('ok')
if(item.z!=null)item.remove('z')
if(item.zc!=null)item.remove('zc')
if(item.e!=null && item.e instanceof String)item.remove(sE)
if(item.l!=null && item.l instanceof String)item.remove('l')
if(item.v!=null)cleanCode(item.v)
if(item.exp!=null)cleanCode(item.exp)
if(item.lo!=null)cleanCode(item.lo)
if(item.lo2!=null)cleanCode(item.lo2)
if(item.lo3!=null)cleanCode(item.lo3)
if(item.ro!=null){
if(item.ro instanceof String || fndEmptyOper((Map)item.ro))item.remove('ro')
else cleanCode(item.ro)
}
if(item.ro2!=null){
if(fndEmptyOper((Map)item.ro2))item.remove('ro2')
else cleanCode(item.ro2)
}
if(item.to!=null){
if(fndEmptyOper((Map)item.to))item.remove('to')
else cleanCode(item.to)
}
if(item.to2!=null){
if(fndEmptyOper((Map)item.to2))item.remove('to2')
else cleanCode(item.to2)
}
for(list in item.findAll{ it.value instanceof List }){
for(itemA in ((List)list.value).findAll{ it instanceof Map })cleanCode(itemA)
}
}
static Boolean fndEmptyOper(Map oper){
if((Integer)oper.size()==3 && (String)oper.t==sC && !oper.d && (String)oper.g==sANY)return true
return false
}
Map deletePiston(){
String meth='deletePiston'
if(eric())log.debug meth
atomicState.pistonDeleted=true
state.active=false
clear1(true,true,true,true) // calls clearMyCache(meth) && clearMyPiston
return [:]
}
private void checkLabel(Map rtD=null){
Boolean act=(Boolean)rtD.active
Boolean dis=!(Boolean)rtD.enabled
String savedLabel=(String)rtD.svLabel
if(savedLabel==sNULL){
log.error "null label"
return
}
String appLbl=savedLabel
if(savedLabel!=sNULL){
if(act && !dis){
app.updateLabel(savedLabel)
}
if(!act || dis){
String tstr='(Paused)'
if(act && dis) tstr='(Disabled) Kill switch is active'
String res=appLbl+" <span style='color:orange'>"+tstr+"</span>"
app.updateLabel(res)
}
}
}
void config(Map data){ // creates a new piston
if(data==null) return
if((String)data.bin!=sNULL){
state.bin=(String)data.bin
app.updateSetting('bin', [type:sTEXT, value:(String)state.bin])
}
if((String)data.author!=null){
state.author=(String)data.author
app.updateSetting('author', [type:sTEXT, value:(String)state.author])
}
if((String)data.initialVersion!=null) state.initialVersion=(String)data.initialVersion
clearMyCache('config')
}
Map setBin(String bin){
if(!bin || !!state.bin){
log.error 'setBin: bad bin'
return [:]
}
state.bin=bin
app.updateSetting('bin', [type:sTEXT, value:bin])
String typ='setBin'
clearMyCache(typ)
return [:]
}
Map pausePiston(){
state.active=false
clearMyCache('pauseP')
LinkedHashMap rtD=getRunTimeData()
Map msg=timer 'Piston successfully stopped', rtD, -1
if((Integer)rtD.logging>0)info 'Stopping piston...', rtD, 0
state.schedules=[]
rtD.stats.nextSchedule=0L
rtD.nextSchedule=0L
state.nextSchedule=0L
unsubscribe()
unschedule()
// state.trace=[:]
state.subscriptions=[:]
if((Integer)rtD.logging>0)info msg, rtD
updateLogs(rtD)
state.active=false
state.state=[:]+(Map)rtD.state
state.remove('lastEvent')
clear1(true,false,false,false) // calls clearMyCache(meth) && clearMyPiston
Map nRtd=shortRtd(rtD)
rtD=null
return nRtd
}
Map resume(LinkedHashMap piston=null){
state.active=true
state.subscriptions=[:]
state.schedules=[]
clearMyCache('resumeP')
LinkedHashMap<String,Object> tmpRtD=getTemporaryRunTimeData(now())
Map msg=timer 'Piston successfully started', tmpRtD, -1
if(piston!=null)tmpRtD.piston=piston
LinkedHashMap rtD=getRunTimeData(tmpRtD, null, true, false) //performs subscribeAll(rtD); reinitializes cache variables
if((Integer)rtD.logging>0)info 'Starting piston... ('+HEversion()+')', rtD, 0
checkVersion(rtD)
if((Integer)rtD.logging>0)info msg, rtD
updateLogs(rtD)
state.state=[:]+(Map)rtD.state
Map nRtd=shortRtd(rtD)
nRtd.result=[active:true, subscriptions:(Map)state.subscriptions]
tmpRtD=null
rtD=null
return nRtd
}
static Map shortRtd(Map rtD){
Map st=[:]+(Map)rtD.state
st.remove('old')
Map myRt=[
id:(String)rtD.id,
active:(Boolean)rtD.active,
category:rtD.category,
stats:[
nextSchedule:(Long)rtD.nextSchedule
],
piston:[
z:(String)rtD.pistonZ
],
state:st,
Cached:(Boolean)rtD.Cached ?: false
]
return myRt
}
Map setLoggingLevel(String level, Boolean clearC=true){
Integer mlogging=level.isInteger()? level.toInteger():0
mlogging=Math.min(Math.max(0,mlogging),3)
app.updateSetting('logging', [type:sENUM, value:mlogging.toString()])
state.logging=mlogging
if(mlogging==0)state.logs=[]
if(clearC) clearMyCache('setLoggingLevel')
return [logging:mlogging]
}
Map setCategory(String category){
state.category=category
clearMyCache('setCategory')
return [category:category]
}
Map test(){
handleEvents([date:new Date(), device:location, name:'test', value:now()])
return [:]
}
Map execute(data, source){
handleEvents([date:new Date(), device:location, name:'execute', value:source!=null ? source : now(), jsonData:data], false)
return [:]
}
Map clickTile(index){
handleEvents([date:new Date(), device:location, name:'tile', value:index])
return (Map)state.state ?: [:]
}
Map clearCache(){
handleEvents([date:new Date(), device:location, name:'clearc', value:now()])
return [:]
}
Map clearLogsQ(){
handleEvents([date:new Date(), device:location, name:'clearl', value:now()])
return [:]
}
private Map getCachedAtomicState(){
Long atomStart=now()
def atomState
atomicState.loadState()
atomState=atomicState.@backingMap
if(settings.logging>2)log.debug "AtomicState generated in ${now() - atomStart}ms"
return atomState
}
@Field volatile static Map<String,Long> lockTimesFLD=[:]
@Field volatile static Map<String,String> lockHolderFLD=[:]
Boolean getTheLock(String qname, String meth=sNULL, Boolean longWait=false){
Long waitT=longWait? 1000L:60L
Boolean wait=false
Integer semaNum=getSemaNum(qname)
String semaSNum=semaNum.toString()
def sema=getSema(semaNum)
while(!((Boolean)sema.tryAcquire())){
// did not get the lock
Long timeL=lockTimesFLD[semaSNum]
if(timeL==null){
timeL=now()
lockTimesFLD[semaSNum]=timeL
lockTimesFLD=lockTimesFLD
}
if(eric())log.warn "waiting for ${qname} ${semaSNum} lock access, $meth, long: $longWait, holder: ${(String)lockHolderFLD[semaSNum]}"
pauseExecution(waitT)
wait=true
if((now() - timeL) > 30000L) {
releaseTheLock(qname)
if(eric())log.warn "overriding lock $meth"
}
}
lockTimesFLD[semaSNum]=now()
lockTimesFLD=lockTimesFLD
lockHolderFLD[semaSNum]=app.id.toString()+sSPC+meth
lockHolderFLD=lockHolderFLD
return wait
}
void releaseTheLock(String qname){
Integer semaNum=getSemaNum(qname)
String semaSNum=semaNum.toString()
def sema=getSema(semaNum)
lockTimesFLD[semaSNum]=null
lockTimesFLD=lockTimesFLD
// lockHolderFLD[semaSNum]=sNULL
// lockHolderFLD=lockHolderFLD
sema.release()
}
@Field static java.util.concurrent.Semaphore theSerialLockFLD=new java.util.concurrent.Semaphore(0)
// Memory Barrier
static void mb(){
if((Boolean)theSerialLockFLD.tryAcquire()){
theSerialLockFLD.release()
}
}
@Field static java.util.concurrent.Semaphore theLock0FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock1FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock2FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock3FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock4FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock5FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock6FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock7FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock8FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock9FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock10FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock11FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock12FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock13FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock14FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock15FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock16FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock17FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock18FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock19FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock20FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock21FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock22FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock23FLD=new java.util.concurrent.Semaphore(1)
@Field static java.util.concurrent.Semaphore theLock24FLD=new java.util.concurrent.Semaphore(1)
static Integer getSemaNum(String name){
if(name==sTCCC)return 22
if(name==sTSLF)return 23
if(name==sTGBL)return 24
Integer stripes=22
if(name.isNumber()) return name.toInteger()%stripes
Integer hash=smear(name.hashCode())
return Math.abs(hash)%stripes
// if(eric())log.info "sema $name # $sema"
}
java.util.concurrent.Semaphore getSema(Integer snum){
switch(snum){
case 0: return theLock0FLD
case 1: return theLock1FLD
case 2: return theLock2FLD
case 3: return theLock3FLD
case 4: return theLock4FLD
case 5: return theLock5FLD
case 6: return theLock6FLD
case 7: return theLock7FLD
case 8: return theLock8FLD
case 9: return theLock9FLD
case 10: return theLock10FLD
case 11: return theLock11FLD
case 12: return theLock12FLD
case 13: return theLock13FLD
case 14: return theLock14FLD
case 15: return theLock15FLD
case 16: return theLock16FLD
case 17: return theLock17FLD
case 18: return theLock18FLD
case 19: return theLock19FLD
case 20: return theLock20FLD
case 21: return theLock21FLD
case 22: return theLock22FLD
case 23: return theLock23FLD
case 24: return theLock24FLD
default: log.error "bad hash result $snum"
return null
}
}
private static Integer smear(Integer hashCode) {
hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12)
return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4)
}
void getCacheLock(String meth=sNULL){
Boolean w=getTheLock(sTCCC,meth+sSPC+sTCL)
}
void releaseCacheLock(){
releaseTheLock(sTCCC)
}
@Field volatile static Map<String,List> theQueuesFLD=[:]
@Field volatile static Map<String,Long> theSemaphoresFLD=[:]
// This can a)lock semaphore, b)wait for semaphore, c)queue event, d)just fall through (no locking, waiting)
private Map lockOrQueueSemaphore(Boolean synchr, event, Boolean queue, Map rtD){
Long tt1=now()
Long startTime=tt1
Long r_semaphore=0L
Long semaphoreDelay=0L
String semaphoreName=sNULL
Boolean didQ=false
Boolean waited=false
if(synchr){
String semaName=app.id.toString()
waited=getTheLock(semaName, sLCK1)
tt1=now()
Long lastSemaphore
Boolean clearC=false
Integer qsize
while(true){
Long t0=(Long)theSemaphoresFLD[semaName]
Long tt0=t0!=null ? t0 : 0L
lastSemaphore=tt0
if(lastSemaphore==0L || tt1-lastSemaphore>100000L){
theSemaphoresFLD[semaName]=tt1
theSemaphoresFLD=theSemaphoresFLD
semaphoreName=semaName
semaphoreDelay=waited ? tt1-startTime:0L
r_semaphore=tt1
break
}
if(queue){
if(event!=null){
Map myEvent=[
t:(Long)((Date)event.date).getTime(),
name:(String)event.name,
value:event.value,
descriptionText:(String)event.descriptionText,
unit:event?.unit,
physical:!!event.physical,
jsonData:event?.jsonData,
]+(event instanceof com.hubitat.hub.domain.Event ? [:]:[
index:event.index,
recovery:event.recovery,
schedule:event.schedule,
contentType:(String)event.contentType,
responseData:event.responseData,
responseCode:event.responseCode,
setRtData:event.setRtData
])
if(event.device!=null){
myEvent.device=[id:event.device?.id, name:event.device?.name, label:event.device?.label]
if(event.device?.hubs!=null){
myEvent.device.hubs=[t:'tt']
}
}
List evtQ=(List)theQueuesFLD[semaName]
evtQ=evtQ!=null ? evtQ:[]
qsize=(Integer)evtQ.size()
if(qsize>12){
clearC=true
}else{
Boolean a=evtQ.push(myEvent)
theQueuesFLD[semaName]=evtQ
theQueuesFLD=theQueuesFLD
didQ=true
}
}
break
}else{
releaseTheLock(semaName)
waited=true
pauseExecution(100L)
Boolean a=getTheLock(semaName, sLCK2)
tt1=now()
}
}
releaseTheLock(semaName)
if(clearC){
error "large queue size ${qsize} clearing", rtD
clear1(true,true,true,true)
}
}
return [
semaphore: r_semaphore,
semaphoreName: semaphoreName,
semaphoreDelay: semaphoreDelay,
waited: waited,
exitOut: didQ
]
}
private LinkedHashMap<String,Object> getTemporaryRunTimeData(Long startTime){
if(thePhysCommandsFLD==null){ //do one time load once
String semName=sTSLF
Boolean a=getTheLock(semName,sGETTRTD,true)
if(thePhysCommandsFLD==null){
Map comparison=Comparisons()
Map vcmd=VirtualCommands()
Map attr=Attributes()
List col=getColors()
Map cmd=PhysicalCommands()
}
releaseTheLock(semName)
}
LinkedHashMap<String,Object> rtD=getDSCache(sGETTRTD)
rtD.temporary=true
rtD.timestamp=startTime
rtD.logs=[[t:startTime]]
rtD.debugLevel=0
rtD.eric=eric1() && (Integer)rtD.logging>2
return rtD
}
@Field volatile static LinkedHashMap<String,LinkedHashMap> theCacheFLD=[:] // each piston has a map in here
private void clearMyCache(String meth=sNULL){
Boolean clrd=false
String appStr=app.id.toString()
String myId=hashId(appStr)
if(!myId)return
String semaName=appStr
String str='clearMyCache'
Boolean a=getTheLock(semaName,str)
getCacheLock(str)
Map t0=(Map)theCacheFLD[myId]
if(t0){
theCacheFLD[myId]=null
clrd=true
t0=null
}
releaseCacheLock()
releaseTheLock(semaName)
if(clrd && eric())log.debug 'clearing piston data cache '+meth
}
private LinkedHashMap<String,Object> getCachedMaps(String meth=sNULL, Boolean retry=true, Boolean Upd=true){
String myId=hashId(app.id)
LinkedHashMap<String,Object> result=(LinkedHashMap<String,Object>)theCacheFLD[myId]
if(result!=null){
if(result.cache instanceof Map && result.build instanceof Integer){
return result
}
String semaName=app.id.toString()
Boolean aa=getTheLock(semaName, sI)
theCacheFLD[myId]=null
releaseTheLock(semaName)
}
if(retry){
LinkedHashMap<String,Object> a=getDSCache(meth,Upd)
if(!Upd)return a
return getCachedMaps(meth,false,Upd)
}
if(eric())log.warn 'cached map nf'
return null
}
private LinkedHashMap<String,Object> getDSCache(String meth, Boolean Upd=true){
String appStr=app.id.toString()
String appId=hashId(appStr)
String myId=appId
LinkedHashMap<String,Object> pC=getParentCache()
LinkedHashMap<String,Object> result=(LinkedHashMap)theCacheFLD[myId]
if(result!=null) result.stateAccess=null
Boolean sendM=false
if(result==null){
String lockTyp='getDSCache'
String semaName=appStr
Boolean a=getTheLock(semaName, lockTyp)
result=(LinkedHashMap)theCacheFLD[myId]
if(result==null){
Long stateStart=now()
if(state.pep==null){ // upgrades of older pistons
LinkedHashMap piston=recreatePiston()
state.pep=piston.o?.pep ? true:false
}
Integer bld=(Integer)state.build
String ttt=(String)state.svLabel
if(ttt==sNULL){
ttt=(String)app.label
if(bld>0){
state.svLabel=ttt
atomicState.svLabel=ttt
}
}
LinkedHashMap<String,Object> t1=[
id: appId,
logging: (Integer)state.logging!=null ? (Integer)state.logging:0,
svLabel: ttt,
name: ttt,
active: (Boolean)state.active,
category: state.category ?: 0,
pep: (Boolean)state.pep,
created: (Long)state.created,
modified: (Long)state.modified,
build: bld,
author: (String)state.author,
bin: (String)state.bin,
logsToHE: (Boolean)settings.logsToHE,
]
Long stateEnd=now()
t1.stateAccess=stateEnd-stateStart
t1.runTimeHis=[]
def atomState=((Boolean)t1.pep)? getCachedAtomicState():state
def t0=(Map)atomState.cache
t1.cache=t0 ? (Map)t0:[:]
t0=(Map)atomState.store
t1.store=t0 ? (Map)t0:[:]
t0=(Map)atomState.state
t1.state=t0 ? (Map)t0:[:]
t0=(String)atomState.pistonZ
t1.pistonZ=t0
t0=(Map)atomState.trace
t1.trace=t0 ? (Map)t0:[:]
t0=(List)atomState.schedules
t1.schedules=t0 ? (List)t0:[]
t1.nextSchedule=(Long)atomState.nextSchedule
t1.lastExecuted=(Long)atomState.lastExecuted
t1.mem=mem()
t0=(List)atomState.logs
t1.logs=t0 ? (List)t0:[]
t0=(Map)atomState.vars
t1.vars=t0 ? [:]+(Map)t0:[:]
t1.cachePersist=[:]
resetRandomValues(t1)
t1.devices= settings.dev && settings.dev instanceof List ? settings.dev.collectEntries{[(hashId(it.id)): it]} : [:]
sendM=true
if(Upd){
t1.Cached=true
theCacheFLD[myId]=t1
}
result=t1
t1=null
t0=null
atomState=null
}
releaseTheLock(semaName)
if(sendM && eric()){
String st=sBLK
if(Upd)st='/cached'
log.debug 'creating'+st+' my piston cache '+meth
}
}
LinkedHashMap<String,Object> rtD=pC+result
pC=null
result=null
if(sendM && rtD.build!=0)checkLabel(rtD)
return rtD
}
@Field volatile static LinkedHashMap<String,LinkedHashMap<String,Object> > theParentCacheFLD = [:]
void clearParentCache(String meth=sNULL){
String lockTyp='clearParentCache'
String semName=sTSLF
String wName = parent.id.toString()
Boolean a=getTheLock(semName, lockTyp)
theParentCacheFLD[wName]=null
getCacheLock(lockTyp)
theCacheFLD=[:] // all pistons reset their cache
theHashMapFLD=[:]
theVirtDevicesFLD=null
releaseCacheLock()
releaseTheLock(semName)
if(eric())log.debug "clearing parent cache and all piston caches $meth"
}
private LinkedHashMap<String,Object> getParentCache(){
String lockTyp='getParentCache'
String wName = parent.id.toString()
LinkedHashMap<String,Object> result=theParentCacheFLD[wName]
if(result==null){
String semName=sTSLF
Boolean a=getTheLock(semName, lockTyp)
result=theParentCacheFLD[wName]
Boolean sendM=false
if(result==null){
Map t0=(Map)parent.getChildPstate()
Map t1=[
coreVersion: (String)t0.sCv,
hcoreVersion: (String)t0.sHv,
powerSource: (String)t0.powerSource,
region: (String)t0.region,
instanceId: (String)t0.instanceId,
settings: (Map)t0.stsettings,
enabled: (Boolean)t0.enabled,
//disabled: !(Boolean)t0.enabled,
logPExec: (Boolean)t0.logPExec,
locationId: (String)t0.locationId,
oldLocationId: hashId(location.id.toString()+'L'), //backwards compatibility
incidents: (List)t0.incidents,
useLocalFuelStreams: (Boolean)t0.useLocalFuelStreams
]
result=t1
theParentCacheFLD[wName]=t1
t1=null
sendM=true
}
releaseTheLock(semName)
if(sendM && eric()){
String mStr='gathering parent cache'
log.debug mStr
}
}
return result
}
private LinkedHashMap<String,Object> getRunTimeData(LinkedHashMap<String,Object> rtD=null, Map retSt=null, Boolean fetchWrappers=false, Boolean shorten=false){
Long timestamp=now()
Long started=timestamp
List logs=[]
Long lstarted=0L
Long lended=0L
LinkedHashMap piston
Integer dbgLevel=0
if(rtD!=null){
timestamp=(Long)rtD.timestamp
logs=rtD.logs!=null ? (List)rtD.logs:[]
lstarted=rtD.lstarted!=null ? (Long)rtD.lstarted:0L
lended=rtD.lended!=null ? (Long)rtD.lended:0L
piston=rtD.piston!=null ? (LinkedHashMap)rtD.piston:null
dbgLevel=rtD.debugLevel!=null ? (Integer)rtD.debugLevel:0
}else rtD=getTemporaryRunTimeData(timestamp)
if(rtD.temporary!=null) rtD.remove('temporary')
LinkedHashMap<String,Object> m1=[semaphore:0L, semaphoreName:sNULL, semaphoreDelay:0L]
if(retSt!=null){
m1.semaphore=(Long)retSt.semaphore
m1.semaphoreName=(String)retSt.semaphoreName
m1.semaphoreDelay=(Long)retSt.semaphoreDelay
}
rtD=rtD+m1
rtD.timestamp=timestamp
rtD.lstarted=lstarted
rtD.lended=lended
//rtD.logs=[]
if(logs!=[] && (Integer)logs.size()>0) rtD.logs=logs
else rtD.logs=[[t: timestamp]]
rtD.debugLevel=dbgLevel
rtD.trace=[t:timestamp, points:[:]]
rtD.stats=[nextSchedule:0L]
rtD.newCache=[:]
rtD.schedules=[]
rtD.cancelations=[statements:[], conditions:[], all:false]
rtD.updateDevices=false
rtD.systemVars=getSystemVariables()
Map atomState=getCachedMaps('getRTD')
atomState=atomState!=null?atomState:[:]
Map st=(Map)atomState.state
rtD.state=st!=null && st instanceof Map ? [:]+st : [old:sBLK, new:sBLK]
rtD.state.old=(String)rtD.state.new
rtD.pStart=now()
if(piston==null) piston=recreatePiston(shorten)
Boolean doSubScribe=!(Boolean)piston.cached
rtD.piston=piston
getLocalVariables(rtD, (List)piston.v, atomState)
piston=null
if(doSubScribe || fetchWrappers){
subscribeAll(rtD, fetchWrappers)
String pisName=app.id.toString()
Map pData=(Map)thePistonCacheFLD[pisName]
if(shorten && pisName!=sBLK && pData!=null && pData.pis==null){
pData.pis=[:]+(LinkedHashMap)rtD.piston
thePistonCacheFLD[pisName]=[:]+pData
pData=null
mb()
if(eric()){
Map pL
Integer t0=0
Integer t1=0
try {
pL=[:]+thePistonCacheFLD
t0=(Integer)pL.size()
t1=(Integer)"${pL}".size()
} catch(a) {
}
pL=null
String mStr=" piston plist is ${t0} elements, and ${t1} bytes".toString()
log.debug 'creating my piston-code-cache'
log.debug "saving"+mStr
if(t1>40000000){
thePistonCacheFLD=[:]
mb()
log.warn "clearing entire"+mStr
}
}
}
}
Long t0=now()
rtD.pEnd=t0
rtD.ended=t0
rtD.generatedIn=t0-started
return rtD
}
private void checkVersion(Map rtD){
String ver=HEversion()
String t0=(String)rtD.hcoreVersion
if(ver!=t0){
String tt0="child app's version($ver)".toString()
String tt1="parent app's version($t0)".toString()
String tt2=' is newer than the '
String msg
if(ver>t0) msg=tt0+tt2+tt1
else msg=tt1+tt2+tt0
warn "WARNING: Results may be unreliable because the "+msg+". Please update both apps to the same version.", rtD
}
if(location.timeZone==null){
error 'Your location is not setup correctly - timezone information is missing. Please select your location by placing the pin and radius on the map, then tap Save, and then tap Done. You may encounter error or incorrect timing until this is fixed.', rtD
}
}
/** EVENT HANDLING **/
void deviceHandler(event){
handleEvents(event)
}
void timeHandler(event){
timeHelper(event, false)
}
void timeHelper(event, Boolean recovery){
handleEvents([date:new Date((Long)event.t), device:location, name:sTIME, value:(Long)event.t, schedule:event, recovery:recovery], !recovery)
}
void executeHandler(event){
handleEvents([date:event.date, device:location, name:'execute', value:event.value, jsonData:event.jsonData])
}
@Field static final Map getPistonLimits=[
schedule: 3000L, // need this or longer remaining execution time to process schedules
scheduleVariance: 970L,
executionTime: 40000L, // time we stop this execution
slTime: 1300L, // time before we start pausing
useBigDelay: 10000L, // transition from short delay to Long delay
taskShortDelay: 150L,
taskLongDelay: 500L,
taskMaxDelay: 1000L,
maxStats: 50,
maxLogs: 50,
]
void handleEvents(event, Boolean queue=true, Boolean callMySelf=false){
Long startTime=now()
LinkedHashMap<String,Object> tmpRtD=getTemporaryRunTimeData(startTime)
Map msg=timer 'Event processed successfully', tmpRtD, -1
String evntName=(String)event.name
String evntVal="${event.value}".toString()
Long eventDelay=Math.round(1.0D*startTime-(Long)((Date)event.date).getTime())
if((Integer)tmpRtD.logging!=0){
String devStr="${event.device?.label ?: event.device?.name ?: location}".toString()
String recStr=evntName==sTIME && (Boolean)event.recovery ? '/recovery':sBLK
String valStr=evntVal+(evntName=='hsmAlert' && evntVal=='rule' ? ', '+(String)event.descriptionText:sBLK)
String mymsg='Received event ['+devStr+'].'+evntName+recStr+' = '+valStr+" with a delay of ${eventDelay}ms, canQueue: ${queue}, calledMyself: ${callMySelf}".toString()
info mymsg, tmpRtD, 0
}
Boolean clearC=evntName=='clearc'
Boolean clearL=evntName=='clearl'
Boolean act=(Boolean)tmpRtD.active
Boolean dis=!(Boolean)tmpRtD.enabled
if(!act || dis){
if((Integer)tmpRtD.logging!=0){
String tstr=' active, aborting piston execution.'
if(!act) msg.m='Piston is not'+tstr+' (Paused)' // this is pause/resume piston
if(dis) msg.m='Kill switch is'+tstr
info msg, tmpRtD
}
updateLogs(tmpRtD)
if(clearL) clear1(true,true,true,false,true)
else if(clearC) clear1(true,false,false,false)
return
}
Boolean myPep=(Boolean)tmpRtD.pep
String appId=(String)tmpRtD.id
Boolean serializationOn=true // on / off switch
Boolean strictSync=true // this could be a setting
Boolean doSerialization=!myPep && (serializationOn || strictSync) && !callMySelf
tmpRtD.lstarted=now()
Map retSt=[ semaphore:0L, semaphoreName:sNULL, semaphoreDelay:0L]
if(doSerialization){
retSt=lockOrQueueSemaphore(doSerialization, event, queue, tmpRtD)
if((Boolean)retSt.exitOut){
if((Integer)tmpRtD.logging!=0){
msg.m='Event queued'
info msg, tmpRtD
}
updateLogs(tmpRtD)
event=null
tmpRtD=null
return
}
if((Long)retSt.semaphoreDelay>0L)warn 'Piston waited for semaphore '+(Long)retSt.semaphoreDelay+'ms', tmpRtD
}
tmpRtD.lended=now()
//measure how Long first state access takes
Long stAccess=0L
if((Integer)tmpRtD.logging>0 && !myPep){
if(tmpRtD.stateAccess==null){
Long stStart=now()
Long b=(Long)state.nextSchedule
def a=(List)state.schedules
Map pEvt=(Map)state.lastEvent
Long stEnd=now()
stAccess=stEnd-stStart
}else stAccess=(Long)tmpRtD.stateAccess
}
tmpRtD.cachePersist=[:]
LinkedHashMap<String,Object> rtD=getRunTimeData(tmpRtD, retSt, false, true)
tmpRtD=null
checkVersion(rtD)
Long theend=now()
Long t0=theend-startTime
Long t1=(Long)rtD.lended-(Long)rtD.lstarted
Long t2=(Long)rtD.generatedIn
Long t3=(Long)rtD.pEnd-(Long)rtD.pStart
Long missing=t0-t1-t2
Long t4=(Long)rtD.lended-startTime
Long t5=theend-(Long)rtD.lended
rtD.curStat=[i:t0, l:t1, r:t2, p:t3, s:stAccess]
if((Integer)rtD.logging>1){
if((Integer)rtD.logging>2)debug "RunTime initialize > ${t0} LockT > ${t1}ms > rtDT > ${t2}ms > pistonT > ${t3}ms (first state access ${missing} $t4 $t5)".toString(), rtD
String adMsg=sBLK
if(eric())adMsg=" (Init: $t0, Lock: $t1, pistonT $t3 first state access $missing ($t4 $t5) $stAccess".toString()
trace "Runtime (${(Integer)"$rtD".size()} bytes) successfully initialized in ${t2}ms (${HEversion()})".toString()+adMsg, rtD
}
resetRandomValues(rtD)
rtD.tPause=0L
rtD.stats.timing=[t:startTime, d:eventDelay>0L ? eventDelay:0L, l:Math.round(1.0D*now()-startTime)]
if(clearC||clearL){
if(clearL) clear1(true,true,true,false,true)
else if(rtD.lastExecuted==null || now()-(Long)rtD.lastExecuted > 3660000L) clear1(true,false,false,false)
}else{
startTime=now()
Map msg2
if((Integer)rtD.logging>0)msg2=timer "Execution stage complete.", rtD, -1
Boolean success=true
Boolean firstTime=true
if(evntName!=sTIME && evntName!=sASYNCREP){
if((Integer)rtD.logging>0)info "Execution stage started", rtD, 1
success=executeEvent(rtD, event)
firstTime=false
}
if(evntName==sTIME && !(Boolean)event.recovery){
rtD.stats.nextSchedule=0L
rtD.nextSchedule=0L
state.nextSchedule=0L
}
Boolean syncTime=true
String myId=(String)rtD.id
while(success && (Long)getPistonLimits.executionTime+(Long)rtD.timestamp-now()>(Long)getPistonLimits.schedule){
List<Map> schedules
Map tt0=getCachedMaps()
if(tt0!=null)schedules=(List<Map>)[]+(List<Map>)tt0.schedules
else schedules=myPep ? (List<Map>)atomicState.schedules:(List<Map>)state.schedules
if(schedules==null || schedules==(List<Map>)[] || (Integer)schedules.size()==0)break
Long t=now()
if(evntName==sASYNCREP){
event.schedule=schedules.sort{ (Long)it.t }.find{ (String)it.d==evntVal }
syncTime=false
}else{
//anything less than .9 seconds in the future is considered due, we'll do some pause to sync with it
//we're doing this because many times, the scheduler will run a job early, usually 0-1.5 seconds early...
evntName=sTIME
evntVal=t.toString()
event=[date:(Date)event.date, device:location, name:evntName, value:t, schedule:schedules.sort{ (Long)it.t }.find{ (Long)it.t<t+(Long)getPistonLimits.scheduleVariance }]
}
if(event.schedule==null) break
schedules.remove(event.schedule)
tt0=getCachedMaps()
if(tt0!=null){
String semaName=app.id.toString()
Boolean aa=getTheLock(semaName, sX)
theCacheFLD[myId].schedules=schedules
releaseTheLock(semaName)
}
tt0=null
if(myPep)atomicState.schedules=schedules
else state.schedules=schedules
if(evntName==sASYNCREP){
if((Boolean)rtD.eric) myDetail rtD, "async event $event"
Integer responseCode=(Integer)event.responseCode
Boolean statOk=responseCode>=200 && responseCode<=299
String eMsg
switch(evntVal){
case sHTTPR:
if(event.schedule.stack!=null){
event.schedule.stack.response=event.responseData
event.schedule.stack.json=event.jsonData
}
setSystemVariableValue(rtD, sHTTPCONTENT, (String)event.contentType)
case sSTOREM:
if(event.setRtData){
for(item in event.setRtData){
rtD[(String)item.key]=item.value
}
}
setSystemVariableValue(rtD, sHTTPSTSCODE, responseCode)
setSystemVariableValue(rtD, sHTTPSTSOK, statOk)
break
case sIFTTM:
setSystemVariableValue(rtD, sIFTTTSTSCODE, responseCode)
setSystemVariableValue(rtD, sIFTTTSTSOK, statOk)
break
case sSENDE:
break
default:
eMsg="unknown "
error eMsg+"async event "+evntVal, rtD
}
evntName=sTIME
event.name=evntName
event.value=t
evntVal=t.toString()
}else{
Integer responseCode=408
Boolean statOk=false
String ttyp=(String)event.schedule.d
Boolean found=true
switch(ttyp){
case sHTTPR:
setSystemVariableValue(rtD, sHTTPCONTENT, sBLK)
if(event.schedule.stack!=null) event.schedule.stack.response=null
case sSTOREM:
setSystemVariableValue(rtD, sHTTPSTSCODE, responseCode)
setSystemVariableValue(rtD, sHTTPSTSOK, statOk)
break
case sSENDE:
break
case sIFTTM:
setSystemVariableValue(rtD, sIFTTTSTSCODE, responseCode)
setSystemVariableValue(rtD, sIFTTTSTSOK, statOk)
break
default:
found=false
break
}
if(found){
error "Timeout Error "+ttyp, rtD
syncTime=true
}
}
//if we have any other pending -3 events (device schedules), we cancel them all
//if(event.schedule.i>0)schedules.removeAll{ (it.s==event.schedule.s) && (it.i==-3)}
if(syncTime && strictSync){
Long delay=Math.round((Long)event.schedule.t-1.0D*now())
if(delay>0L && delay<(Long)getPistonLimits.scheduleVariance){
if((Integer)rtD.logging>1)trace "Synchronizing scheduled event, waiting for ${delay}ms".toString(), rtD
pauseExecution(delay)
}
}
if(firstTime&&(Integer)rtD.logging>0){
msg2=timer "Execution stage complete.", rtD, -1
info "Execution stage started", rtD, 1
}
success=executeEvent(rtD, event)
syncTime=true
firstTime=false
}
rtD.stats.timing.e=Math.round(1.0D*now()-startTime)
if((Integer)rtD.logging>0)info msg2, rtD
if(!success)msg.m='Event processing failed'
if(eric())msg.m=(String)msg.m+' Total Pauses ms: '+((Long)rtD.tPause).toString()
finalizeEvent(rtD, msg, success)
if((Boolean)rtD.logPExec && (Map)rtD.currentEvent!=null){
String desc='webCore piston \''+(String)app.label+'\' was executed'
sendLocationEvent(name:'webCoRE', value:'pistonExecuted', isStateChange:true, displayed:false, linkText:desc, descriptionText:desc, data:[
id:appId,
name:(String)app.label,
event:[date:new Date((Long)rtD.currentEvent.date), delay:(Long)rtD.currentEvent.delay, duration:now()-(Long)rtD.currentEvent.date, device:"${rtD.event.device}".toString(), name:(String)rtD.currentEvent.name, value:rtD.currentEvent.value, physical:(Boolean)rtD.currentEvent.physical, index:(Integer)rtD.currentEvent.index],
state:[old:(String)rtD.state.old, new:(String)rtD.state.new]
])
}
}
for(String foo in heData) rtD.remove(foo)
// any queued events?
String msgt
if((Integer)rtD.logging>2)msgt='Exiting'
String semName=(String)rtD.semaphoreName
while(doSerialization && semName!=sNULL){
Boolean a=getTheLock(semName, sHNDLEVT)
List<Map> evtQ=(List<Map>)theQueuesFLD[semName]
if(evtQ==null || evtQ==[]){
if((Long)theSemaphoresFLD[semName]<=(Long)rtD.semaphore){
if((Integer)rtD.logging>2) msgt='Released Lock and exiting'
theSemaphoresFLD[semName]=0L
theSemaphoresFLD=theSemaphoresFLD
}
releaseTheLock(semName)
break
}
List<Map> evtList=evtQ.sort{ (Long)it.t }
Map theEvent=evtList.remove(0)
theQueuesFLD[semName]=evtList
theQueuesFLD=theQueuesFLD
releaseTheLock(semName)
Integer qsize=(Integer)evtQ.size()
if(qsize>8) log.error "large queue size ${qsize}".toString()
theEvent.date=new Date((Long)theEvent.t)
handleEvents(theEvent, false, true)
}
if((Integer)rtD.logging>2) log.debug msgt
if((Boolean)rtD.updateDevices) clearMyCache('updateDeviceList')
data=rtD.collect{ it.key }
for(item in data)rtD.remove((String)item)
event=null
rtD=null
}
@Field static final List<String> heData=[ 'event', 'currentEvent', 'state', 'created', 'modified', 'sunTimes']
private Boolean executeEvent(Map rtD, event){
String myS
if((Boolean)rtD.eric){
myS='executeEvent'
myDetail rtD, myS, 1
}
try{
/* if(event instanceof com.hubitat.hub.domain.Event){
Map myEvent=[
date:(Date)event.date,
name:(String)event.name,
value:event.value,
descriptionText:(String)event.descriptionText,
unit:event.unit,
physical:event.physical,
jsonData:event.jsonData,
]
if(event.device!=null){
myEvent.device=[id:event.device?.id, name:event.device?.name, label:event.device?.label]
if(event.device?.hubs!=null){
myEvent.device.hubs=[t:'tt']
}
}
rtD.event=myEvent
}else*/ rtD.event=event
Map pEvt=(Map)state.lastEvent
if(pEvt==null)pEvt=[:]
rtD.previousEvent=pEvt
String evntName=(String)event.name
Integer index=0 //event?.index ?: 0
if(event.jsonData!=null){
Map attribute=Attributes()[evntName]
String attrI=attribute!=null ? (String)attribute.i:sNULL
if(attrI!=sNULL && event.jsonData[attrI]){ // .i is the attribute to lookup
index=event.jsonData[attrI]
}
if(!index)index=1
}
Map srcEvent=null
rtD.args=[:]
Map sysV=(Map)rtD.systemVars
if(event!=null){
rtD.args= evntName==sTIME && event.schedule!=null && event.schedule.args!=null && event.schedule.args instanceof Map ? (Map)event.schedule.args:(event.jsonData!=null ? event.jsonData:[:])
if(evntName==sTIME && event.schedule!=null){
srcEvent=(Map)event.schedule.evt
Map tMap=(Map)event.schedule.stack
if(tMap!=null){
sysV[sDLLRINDX].v=(Double)tMap.index
sysV[sDLLRDEVICE].v=(List)tMap.device
sysV[sDLLRDEVS].v=(List)tMap.devices
rtD.json=tMap.json ?: [:]
rtD.response=tMap.response ?: [:]
index=srcEvent?.index ?: 0
// more to restore here?
rtD.systemVars=sysV
}
}
}
setSystemVariableValue(rtD, sDOLARGS, rtD.args)
sysV=(Map)rtD.systemVars
String theDevice=srcEvent!=null ? (String)srcEvent.device:sNULL
def theDevice1=theDevice==sNULL && event.device ? event.device.id:null
String theFinalDevice=theDevice!=sNULL ? theDevice : (theDevice1!=null ? (!isDeviceLocation(event.device) ? hashId(theDevice1.toString()) : (String)rtD.locationId) : (String)rtD.locationId)
Map myEvt=[
date:(Long)((Date)event.date).getTime(),
delay:rtD.stats?.timing?.d ? (Long)rtD.stats.timing.d : 0L,
device:theFinalDevice,
index:index
]
if(srcEvent!=null){
myEvt=myEvt + [
name:(String)srcEvent.name,
value:srcEvent.value,
descriptionText:(String)srcEvent.descriptionText,
unit:srcEvent.unit,
physical:(Boolean)srcEvent.physical,
]
}else{
myEvt=myEvt + [
name:evntName,
value:event.value,
descriptionText:(String)event.descriptionText,
unit:event.unit,
physical:!!event.physical,
]
}
rtD.currentEvent=myEvt
state.lastEvent=myEvt
rtD.conditionStateChanged=false
rtD.pistonStateChanged=false
rtD.ffTo=0
rtD.statementLevel=0
rtD.break=false
rtD.resumed=false
rtD.terminated=false
if(evntName==sTIME){
rtD.ffTo=(Integer)event.schedule.i
}
sysV[sPEVDATE].v=pEvt.date ?: now()
sysV[sPEVDELAY].v=pEvt.delay ?: 0L
sysV[sPEVDEV].v=[pEvt.device]
sysV[sPEVDEVINDX].v=pEvt.index ?: 0
sysV[sPEVATTR].v=pEvt.name ?: sBLK
sysV[sPEVDESC].v=pEvt.descriptionText ?: sBLK
sysV[sPEVVALUE].v=pEvt.value ?: sBLK
sysV[sPEVUNIT].v=pEvt.unit ?: sBLK
sysV[sPEVPHYS].v=!!pEvt.physical
sysV[sCURDATE].v=(Long)myEvt.date
sysV[sCURDELAY].v=(Long)myEvt.delay
sysV[sCURDEV].v=[myEvt.device]
sysV[sCURDEVINDX].v=myEvt.index!=sBLK && myEvt.index!=null? (Integer)myEvt.index:0
sysV[sCURATTR].v=(String)myEvt.name
sysV[sCURDESC].v=(String)myEvt.descriptionText
sysV[sCURVALUE].v=myEvt.value
sysV[sCURUNIT].v=myEvt.unit
sysV[sCURPHYS].v=(Boolean)myEvt.physical
rtD.systemVars=sysV
rtD.stack=[c: 0, s: 0, cs:[], ss:[]]
Boolean ended=false
try{
Boolean allowed=!rtD.piston.r || rtD.piston.r.length==0 || evaluateConditions(rtD, (Map)rtD.piston, sR, true)
rtD.restricted=!rtD.piston.o?.aps && !allowed //allowPreScheduled tasks to execute during restrictions
if(allowed || (Integer)rtD.ffTo!=0){
if((Integer)rtD.ffTo==-3){
//device related time schedules
if(!(Boolean)rtD.restricted){
def data=event.schedule.d
if(data!=null && (String)data.d && (String)data.c){
//we have a device schedule, execute it
def device=getDevice(rtD, (String)data.d)
if(device!=null){
//executing scheduled physical command
//used by fades, flashes, etc.
executePhysicalCommand(rtD, device, (String)data.c, data.p, 0L, sNULL, true)
}
}
}
}else{
if(executeStatements(rtD, (List)rtD.piston.s)){
ended=true
tracePoint(rtD, sEND, 0L, 0)
}
processSchedules rtD
}
}else{
if((Integer)rtD.logging>2)debug 'Piston execution aborted due to restrictions in effect', rtD
//we need to run through all to update stuff
rtD.ffTo=-9
Boolean a=executeStatements(rtD, (List)rtD.piston.s)
ended=true
tracePoint(rtD, sEND, 0L, 0)
processSchedules rtD
}
if(!ended)tracePoint(rtD, sBREAK, 0L, 0)
}catch (all){
error 'An error occurred while executing the event: ', rtD, -2, all
}
if((Boolean)rtD.eric) myDetail rtD, myS+' Result: TRUE', -1
return true
}catch(all){
error 'An error occurred within executeEvent: ', rtD, -2, all
}
processSchedules rtD
return false
}
@Field static final List<String> cleanData=[ 'allDevices', 'cachePersist', 'mem', 'break', 'powerSource', 'oldLocationId', 'incidents', 'lstarted', 'lended', 'pStart', 'pEnd', 'generatedIn', 'ended', 'semaphoreDelay', 'vars', 'stateAccess', 'author', 'bin', 'build', 'newCache', 'mediaData', 'weather', 'logs', 'trace', 'systemVars', 'localVars', 'currentAction', 'previousEvent', 'json', 'response', 'cache', 'store', 'settings', 'locationModeId', 'locationId', 'coreVersion', 'hcoreVersion', 'cancelations', 'conditionStateChanged', 'pistonStateChanged', 'ffTo', 'resumed', 'terminated', 'instanceId', 'wakingUp', 'statementLevel', 'args', 'nfl', 'temp' ]
private void finalizeEvent(Map rtD, Map initialMsg, Boolean success=true){
Long startTime=now()
Boolean myPep=(Boolean)rtD.pep
processSchedules(rtD, true)
if(success){
if((Integer)rtD.logging>0)info initialMsg, rtD
}else error initialMsg, rtD
updateLogs(rtD, (Long)rtD.timestamp)
String myId=(String)rtD.id
rtD.trace.d=Math.round(1.0D*now()-(Long)rtD.trace.t)
//flush the new cache value
for(item in (Map)rtD.newCache) ((Map)rtD.cache)[(String)item.key]=item.value
//overwrite state, might have changed meanwhile
Map t0=getCachedMaps()
String str='finalize '
String semaName=app.id.toString()
if(t0!=null){
Boolean aa=getTheLock(semaName, str)
theCacheFLD[myId].cache=[:]+(Map)rtD.cache
theCacheFLD[myId].store=[:]+(Map)rtD.store
theCacheFLD[myId].state=[:]+(Map)rtD.state
theCacheFLD[myId].trace=[:]+(Map)rtD.trace
releaseTheLock(semaName)
}
if(myPep){
atomicState.cache=(Map)rtD.cache
atomicState.store=(Map)rtD.store
atomicState.state=[:]+(Map)rtD.state
atomicState.trace=(Map)rtD.trace
}else{
state.cache=(Map)rtD.cache
state.store=(Map)rtD.store
state.state=[:]+(Map)rtD.state
state.trace=(Map)rtD.trace
}
//remove large stuff
for(String foo in cleanData) rtD.remove(foo)
if(!(rtD.event instanceof com.hubitat.hub.domain.Event)){
if(rtD.event?.responseData)rtD.event.responseData=[:]
if(rtD.event?.jsonData)rtD.event.jsonData=[:]
if(rtD.event?.setRtData)rtD.event.setRtData=[:]
if(rtD.event?.schedule?.stack)rtD.event.schedule.stack=[:]
}
if((Boolean)rtD.updateDevices) updateDeviceList(rtD, rtD.devices*.value.id)
rtD.remove('devices')
Boolean a
if(rtD.gvCache!=null || rtD.gvStoreCache!=null){
LinkedHashMap tpiston=(LinkedHashMap)rtD.piston
rtD.piston=[:]
rtD.piston.z=(String)tpiston.z
tpiston=null
if(rtD.gvCache!=null){
String lockTyp='finalize'
String semName=sTGBL
String wName = parent.id.toString()
a=getTheLock(semName, lockTyp)
for(var in rtD.gvCache){
Map vars=globalVarsFLD[wName]
String varName=(String)var.key
if(varName && (Boolean)varName.startsWith(sAT) && vars[varName] && var.value.v!=vars[varName].v){
globalVarsFLD[wName][varName].v=var.value.v
globalVarsFLD=globalVarsFLD
}
}
releaseTheLock(semName)
}
parent.pCallupdateRunTimeData(rtD)
rtD.remove('gvCache')
rtD.remove('gvStoreCache')
rtD.initGStore=false
}else{
Map myRt=shortRtd(rtD)
myRt.t=now()
parent.pCallupdateRunTimeData(myRt)
}
rtD.piston=null
rtD.stats.timing.u=Math.round(1.0D*now()-startTime)
//update graph data
Map stats
if(myPep)stats=(Map)atomicState.stats
else stats=(Map)state.stats
stats=stats ?: [:]
List tlist=(List)stats.timing ?: []
Map lastST= (Integer)tlist.size() ? [:]+(Map)tlist.last() : null
Map newMap=[:]+(Map)rtD.stats.timing
if(lastST && newMap){
lastST.t=(Long)newMap.t-10L
a=tlist.push(lastST)
}
a=tlist.push(newMap)
Integer t1=settings.maxStats!=null ? (Integer)settings.maxStats: (Integer)getPistonLimits.maxStats
if(t1<=0)t1=(Integer)getPistonLimits.maxStats
if(t1<2)t1=2
Integer t2=(Integer)tlist.size()
if(t2>t1)tlist=tlist[t2-t1..t2-1]
stats.timing=tlist
if(myPep)atomicState.stats=stats
else state.stats=stats
rtD.stats.timing=null
t0=getCachedMaps()
if(t0!=null){
Long totTime=Math.round(now()*1.0D-(Long)rtD.timestamp)
t1=20
String t4=mem()
Boolean aa=getTheLock(semaName, str+sONE)
theCacheFLD[myId].mem=t4
theCacheFLD[myId].runStats=[:]+(Map)rtD.curStat
List hisList=(List)theCacheFLD[myId].runTimeHis
Boolean b=hisList.push(totTime)
t2=(Integer)hisList.size()
if(t2>t1) hisList=hisList[t2-t1..t2-1]
theCacheFLD[myId].runTimeHis=hisList
releaseTheLock(semaName)
}
}
private void processSchedules(Map rtD, Boolean scheduleJob=false){
Boolean myPep=(Boolean)rtD.pep
List<Map> schedules
Map t0=getCachedMaps()
if(t0!=null)schedules=(List<Map>)[]+(List<Map>)t0.schedules
else schedules=myPep ? (List<Map>)atomicState.schedules:(List<Map>)state.schedules
//if automatic piston states, we set it based on the autoNew - if any
if(rtD.piston.o?.mps==null || !rtD.piston.o.mps) rtD.state.new=(String)rtD.state.autoNew ?: sTRUE
rtD.state.old=(String)rtD.state.new
if((Boolean)rtD.cancelations.all) Boolean a=schedules.removeAll{ (Integer)it.i>0 }
//cancel statements
Boolean b=schedules.removeAll{ Map schedule -> !!((List<Map>)rtD.cancelations.statements).find{ Map cancelation -> (Integer)cancelation.id==(Integer)schedule.s && (!cancelation.data || ((String)cancelation.data==(String)schedule.d))}}
//cancel on conditions
for(Integer cid in (List<Integer>)rtD.cancelations.conditions){
Boolean a=schedules.removeAll{ cid in (List)it.cs }
}
//cancel on piston state change
if((Boolean)rtD.pistonStateChanged){
Boolean a=schedules.removeAll{ (Integer)it.ps!=0 }
}
rtD.cancelations=[statements:[], conditions:[], all:false]
schedules=(schedules+(List<Map>)rtD.schedules)//.sort{ (Long)it.t }
if(myPep)atomicState.schedules=schedules
else state.schedules=(List<Map>)[]+schedules
String myId=(String)rtD.id
String semaName=app.id.toString()
t0=getCachedMaps()
if(t0!=null){
Boolean aa=getTheLock(semaName, sT)
theCacheFLD[myId].schedules=(List<Map>)[]+schedules
releaseTheLock(semaName)
}
if(scheduleJob){
Long nextT=0L
Integer ssz=(Integer)schedules.size()
if(ssz>0){
Map tnext=((List<Map>)schedules).sort{ (Long)it.t }[0]
nextT=(Long)tnext.t
Long t=Math.round((nextT-now())/1000.0D)
t=(t<1L ? 1L:t)
runIn(t, timeHandler, [data: tnext])
if((Integer)rtD.logging>0) info 'Setting up scheduled job for '+formatLocalTime(nextT)+' (in '+t.toString()+'s)' + ((ssz)>1 ? ', with ' + (ssz-1).toString() + ' more job' + (ssz>2 ? sS : sBLK) + ' pending' : sBLK), rtD
}
if(nextT==0L && (Long)rtD.nextSchedule!=0L){
unschedule(timeHandler)
}
rtD.nextSchedule=nextT
rtD.stats.nextSchedule=nextT
state.nextSchedule=nextT
t0=getCachedMaps()
if(t0!=null){
Boolean aa=getTheLock(semaName, sT+sONE)
theCacheFLD[myId].nextSchedule=nextT
releaseTheLock(semaName)
}
}
rtD.schedules=[]
}
private void updateLogs(Map rtD, Long lastExecute=null){
if(!rtD || !rtD.logs)return
String myId=(String)rtD.id
Map cacheMap
String semaName=app.id.toString()
if(lastExecute!=null){
state.lastExecuted=lastExecute
cacheMap=getCachedMaps()
if(cacheMap!=null){
Boolean aa=getTheLock(semaName, sE)
theCacheFLD[myId].lastExecuted=lastExecute
theCacheFLD[myId].temp=[:]+(Map)rtD.temp
theCacheFLD[myId].cachePersist=[:]+(Map)rtD.cachePersist
releaseTheLock(semaName)
}
}
if((Integer)((List)rtD.logs).size()>1){
Boolean myPep=(Boolean)rtD.pep
Integer t1=settings.maxLogs!=null ? (Integer)settings.maxLogs:(Integer)getPistonLimits.maxLogs
if(t1<0)t1=(Integer)getPistonLimits.maxLogs
List t0
cacheMap=getCachedMaps()
if(cacheMap!=null)t0=[]+(List)cacheMap.logs
else t0=myPep ? (List)atomicState.logs:(List)state.logs
List logs=[]+(List)rtD.logs+t0
if(t1>=0){
Integer t2=(Integer)logs.size()
if(t1==0 || t2==0) logs=[]
else{
if(t1< t2-1) logs=logs[0..t1]
if(t1>5 && (Integer)state.toString().size()>75000){
t1 -= Math.min(50, Math.round(t1/2.0D))
logs=logs[0..t1]
if(!myPep) state.logs=logs //this mixes state and AS
}
}
}
cacheMap=getCachedMaps()
if(cacheMap!=null){
Boolean aa=getTheLock(semaName, sE+sONE)
theCacheFLD[myId].logs=logs
releaseTheLock(semaName)
}
if(myPep)atomicState.logs=logs
else state.logs=logs
}
rtD.logs=[]
}
private Boolean executeStatements(Map rtD, List<Map> statements, Boolean async=false){
rtD.statementLevel=(Integer)rtD.statementLevel+1
for(Map statement in statements){
//only execute statements that are enabled
Boolean disab=statement.di!=null && (Boolean)statement.di
if(!disab && !executeStatement(rtD, statement, async)){
//stop processing
rtD.statementLevel=(Integer)rtD.statementLevel-1
return false
}
}
//continue processing
rtD.statementLevel=(Integer)rtD.statementLevel-1
return true
}
private Boolean executeStatement(Map rtD, Map statement, Boolean async=false){
//if rtD.ffTo is a positive, non-zero number, we need to fast forward through all
//branches until we find the task with an id equal to that number, then we play nicely after that
if(statement==null)return false
Integer statementNum=statement.$!=null ? (Integer)statement.$:0
if((Integer)rtD.ffTo==0){
String sMsg="Skipping execution for statement #${statementNum} because "
switch ((String)statement.tep){ // Task Execution Policy
case sC:
if(!(Boolean)rtD.conditionStateChanged){
if((Integer)rtD.logging>2)debug sMsg+'condition state did not change', rtD
return true
}
break
case sP:
if(!(Boolean)rtD.pistonStateChanged){
if((Integer)rtD.logging>2)debug sMsg+'piston state did not change', rtD
return true
}
break
case sB:
if( !(Boolean)rtD.conditionStateChanged && !(Boolean)rtD.pistonStateChanged){
if((Integer)rtD.logging>2)debug sMsg+'neither condition state nor piston state changed', rtD
return true
}
break
}
}
String mySt
if((Boolean)rtD.eric){
mySt='executeStatement '+(String)statement.t
myDetail rtD, mySt, 1
}
Boolean a=((List<Integer>)rtD.stack.ss).push((Integer)rtD.stack.s)
rtD.stack.s=statementNum
Long t=now()
Boolean value=true
Integer c=(Integer)rtD.stack.c
Boolean stacked=true /* cancelable on condition change */
if(stacked)a=((List<Integer>)rtD.stack.cs).push(c)
Boolean svCSC=(Boolean)rtD.conditionStateChanged
//def parentAsync=async
Double svIndex=(Double)rtD.systemVars[sDLLRINDX].v
List svDevice=(List)rtD.systemVars[sDLLRDEVICE].v
Boolean selfAsync= (String)statement.a==sONE || (String)statement.t==sEVERY || (String)statement.t==sON // execution method
async=async || selfAsync
Boolean myPep=(Boolean)rtD.pep
Boolean perform=false
Boolean repeat=true
Double index=null
Boolean allowed=!(List)statement.r || ((List)statement.r).length==0 || evaluateConditions(rtD, statement, sR, async)
if(allowed || (Integer)rtD.ffTo!=0){
while(repeat){
switch ((String)statement.t){
case sEVERY:
//we override current condition that child statements can cancel on it
Boolean ownEvent= rtD.event!=null && (String)rtD.event.name==sTIME && rtD.event.schedule!=null && (Integer)rtD.event.schedule.s==statementNum && (Integer)rtD.event.schedule.i<0
List<Map> schedules
Map t0=getCachedMaps()
if(t0!=null)schedules=[]+(List<Map>)t0.schedules
else schedules=myPep ? (List<Map>)atomicState.schedules:(List<Map>)state.schedules
if(ownEvent || !schedules.find{ (Integer)it.s==statementNum }){
//if the time has come for our timer, schedule the next timer
//if no next time is found quick enough, a new schedule with i=-2 will be setup so that a new attempt can be made at a later time
if(ownEvent)rtD.ffTo=0
scheduleTimer(rtD, statement, ownEvent ? (Long)rtD.event.schedule.t:0L)
}
rtD.stack.c=statementNum
if(ownEvent)rtD.ffTo=0
if((Integer)rtD.ffTo!=0 || (ownEvent && allowed && !(Boolean)rtD.restricted)){
//we don't want to run this if there are piston restrictions in effect
//we only execute the every if i=-1 (for rapid timers with large restrictions i.e. every second, but only on Mondays)we need to make sure we don't block execution while trying
//to find the next execution scheduled time, so we give up after too many attempts and schedule a rerun with i=-2 to give us the chance to try again at that later time
if((Integer)rtD.ffTo!=0 || (Integer)rtD.event.schedule.i==-1)a=executeStatements(rtD, (List)statement.s, true)
//we always exit a timer, this only runs on its own schedule, nothing else is executed
if(ownEvent)rtD.terminated=true
value=false
break
}
value=true
break
case sREPEAT:
//we override current condition that child statements can cancel on it
rtD.stack.c=statementNum
if(!executeStatements(rtD, (List)statement.s, async)){
//stop processing
value=false
if((Integer)rtD.ffTo==0)break
}
value=true
perform= !evaluateConditions(rtD, statement, sC, async)
break
case sON:
perform=false
if((Integer)rtD.ffTo==0){
//look to see if any of the event matches
String deviceId= rtD.event.device!=null ? hashId(rtD.event.device.id):sNULL
for(event in statement.c){
def operand=event.lo
if(operand!=null && (String)operand.t){
switch ((String)operand.t){
case sP:
if(deviceId!=sNULL && (String)rtD.event.name==(String)operand.a && (List)operand.d!=[] && deviceId in expandDeviceList(rtD, (List)operand.d, true)) perform=true
break
case sV:
if((String)rtD.event.name==(String)operand.v) perform=true
break
case sX:
String operX=(String)operand.x
if(rtD.event.value==operX && (String)rtD.event.name==(String)rtD.instanceId+sDOT+operX) perform=true
break
}
}
if(perform)break
}
}
value= (Integer)rtD.ffTo!=0 || perform ? executeStatements(rtD, (List)statement.s, async):true
break
case sIF:
case sWHILE:
//check conditions for if and while
perform=evaluateConditions(rtD, statement, sC, async)
//we override current condition that child statements can cancel on it
rtD.stack.c=statementNum
if((Integer)rtD.ffTo==0 && !rtD.piston.o?.mps && (String)statement.t==sIF && (Integer)rtD.statementLevel==1 && perform){
//automatic piston state
rtD.state.autoNew=sTRUE
}
if(perform || (Integer)rtD.ffTo!=0){
if((String)statement.t in [sIF, sWHILE]){
if(!executeStatements(rtD, (List)statement.s, async)){
//stop processing
value=false
if((Integer)rtD.ffTo==0)break
}
value=true
if((Integer)rtD.ffTo==0)break
}
}
if(!perform || (Integer)rtD.ffTo!=0){
if((String)statement.t==sIF){
//look for else-ifs
for(Map elseIf in (List<Map>)statement.ei){
perform=evaluateConditions(rtD, elseIf, sC, async)
if(perform || (Integer)rtD.ffTo!=0){
if(!executeStatements(rtD, (List)elseIf.s, async)){
//stop processing
value=false
if((Integer)rtD.ffTo==0)break
}
value=true
if((Integer)rtD.ffTo==0)break
}
}
if((Integer)rtD.ffTo==0 && !rtD.piston.o?.mps && (Integer)rtD.statementLevel==1){
//automatic piston state
rtD.state.autoNew=sFALSE
}
if((!perform || (Integer)rtD.ffTo!=0) && !executeStatements(rtD, (List)statement.e, async)){
//stop processing
value=false
if((Integer)rtD.ffTo==0)break
}
}
}
break
case sFOR:
case sEACH:
List devices=[]
Double startValue=0.0D
Double endValue
Double stepValue=1.0D
Integer dsiz=(Integer)devices.size()
if((String)statement.t==sEACH){
List t0=(List)((Map)evaluateOperand(rtD, null, (Map)statement.lo)).v
devices=t0 ?: []
dsiz=(Integer)devices.size()
endValue=dsiz-1.0D
}else{
startValue=(Double)evaluateScalarOperand(rtD, statement, (Map)statement.lo, null, sDCML).v
endValue=(Double)evaluateScalarOperand(rtD, statement, (Map)statement.lo2, null, sDCML).v
Double t0=(Double)evaluateScalarOperand(rtD, statement, (Map)statement.lo3, null, sDCML).v
stepValue=t0 ?: 1.0D
}
String counterVariable=(String)getVariable(rtD, (String)statement.x).t!=sERROR ? (String)statement.x:sNULL
String sidx='f:'+statementNum.toString()
if( (startValue<=endValue && stepValue>0.0D) || (startValue>=endValue && stepValue<0.0D) || (Integer)rtD.ffTo!=0){
//initialize the for loop
if((Integer)rtD.ffTo!=0)index=(Double)cast(rtD, ((Map)rtD.cache)[sidx], sDCML)
if(index==null){
index=(Double)cast(rtD, startValue, sDCML)
//index=startValue
rtD.cache[sidx]=index
}
rtD.systemVars[sDLLRINDX].v=index
if((String)statement.t==sEACH && ((Integer)rtD.ffTo==0||(Integer)rtD.ffTo==-9))setSystemVariableValue(rtD, sDLLRDEVICE, index<dsiz ? [devices[index.toInteger()]]:[])
if(counterVariable!=sNULL && (Integer)rtD.ffTo==0)def m=setVariable(rtD, counterVariable, (String)statement.t==sEACH ? (index<dsiz ? [devices[index.toInteger()]]:[]):index)
//do the loop
perform=executeStatements(rtD, (List)statement.s, async)
if(!perform){
//stop processing
value=false
if((Boolean)rtD.break){
//we reached a break, so we really want to continue execution outside of the for
value=true
rtD.break=false
//perform=false
}
break
}
//don't do the rest if we're fast forwarding
if((Integer)rtD.ffTo!=0)break
index=index+stepValue
rtD.systemVars[sDLLRINDX].v=index
if((String)statement.t==sEACH && (Integer)rtD.ffTo==0)setSystemVariableValue(rtD, sDLLRDEVICE, index<dsiz ? [devices[index.toInteger()]]:[])
if(counterVariable!=sNULL && (Integer)rtD.ffTo==0)def n=setVariable(rtD, counterVariable, (String)statement.t==sEACH ? (index<dsiz ? [devices[index.toInteger()]]:[]):index)
rtD.cache[sidx]=index
if((stepValue>0.0D && index>endValue) || (stepValue<0.0D && index<endValue)){
perform=false
break
}
}
break
case sSWITCH:
Map lo=[operand: (Map)statement.lo, values: (List)evaluateOperand(rtD, statement, (Map)statement.lo)]
//go through all cases
Boolean found=false
Boolean implicitBreaks= (String)statement.ctp==sI // case traversal policy
Boolean fallThrough=!implicitBreaks
perform=false
if((Integer)rtD.logging>2)debug "Evaluating switch with values $lo.values", rtD
for(Map _case in (List<Map>)statement.cs){
Map ro=[operand: (Map)_case.ro, values: (List)evaluateOperand(rtD, _case, (Map)_case.ro)]
Map ro2= (String)_case.t==sR ? [operand: (Map)_case.ro2, values: (List)evaluateOperand(rtD, _case, (Map)_case.ro2, null, false, true)]:null
perform=perform || evaluateComparison(rtD, ((String)_case.t==sR ? 'is_inside_of_range' : 'is'), lo, ro, ro2)
found=found || perform
if(perform || (found && fallThrough)|| (Integer)rtD.ffTo!=0){
Integer ffTo=(Integer)rtD.ffTo
if(!executeStatements(rtD, (List)_case.s, async)){
//stop processing
value=false
if((Boolean)rtD.break){
//we reached a break, so we really want to continue execution outside of the switch
value=true
found=true
fallThrough=false
rtD.break=false
}
if((Integer)rtD.ffTo==0){
break
}
}
//if we determine that the fast forwarding ended during this execution, we assume found is true
found=found || (ffTo!=(Integer)rtD.ffTo)
value=true
//if implicit breaks
if(implicitBreaks && (Integer)rtD.ffTo==0){
fallThrough=false
break
}
}
}
if(statement.e && ((List)statement.e).length && (value || (Integer)rtD.ffTo!=0) && (!found || fallThrough || (Integer)rtD.ffTo!=0)){
//no case found, let's do the default
if(!executeStatements(rtD, (List)statement.e, async)){
//stop processing
value=false
if((Boolean)rtD.break){
//we reached a break, so we really want to continue execution outside of the switch
value=true
rtD.break=false
}
if((Integer)rtD.ffTo==0)break
}
}
break
case sACTION:
value=executeAction(rtD, statement, async)
break
case sDO:
value=executeStatements(rtD, (List)statement.s, async)
break
case sBREAK:
if((Integer)rtD.ffTo==0){
rtD.break=true
}
value=false
break
case sEXIT:
if((Integer)rtD.ffTo==0){
vcmd_setState(rtD, null, [(String)cast(rtD, ((Map)evaluateOperand(rtD, null, (Map)statement.lo)).v, sSTR)])
rtD.terminated=true
}
value=false
break
}
//break the loop
if((Integer)rtD.ffTo!=0 || (String)statement.t==sIF)perform=false
//is this statement a loop
Boolean loop=((String)statement.t in [sWHILE, sREPEAT, sFOR, sEACH])
if(loop && !value && (Boolean)rtD.break){
//someone requested a break from the loop, we're doing it
rtD.break=false
//but we're allowing the rest to continue
value=true
perform=false
}
//do we repeat the loop?
repeat=perform && value && loop && (Integer)rtD.ffTo==0
Long overBy=checkForSlowdown(rtD)
if(overBy>0L){
Long delay=(Long)getPistonLimits.taskShortDelay
if(overBy>(Long)getPistonLimits.useBigDelay){
delay=(Long)getPistonLimits.taskLongDelay
}