Timer progress/remaining in sitemap

Hey!
I am using timers in my rules for a lot of things. In most cases it would be nice to have these in the sitemap.
Let’s take this as an example: I have one rule turning the corridor light on when I’m entering the apartment (let’s say at 18:00). A timer of 15 minutes will turn it off again (18:15), quite handy.
In the sitemap it would make things more transparent if the timed action was indicated. What I can do:

  1. set and display some String “timer running”, that’s easy.
  2. set and display some String “turning off at 18:15”, still quite easy.
  3. set and display some String “turning off in 9 minutes”, more complicated
  4. set and dynamically display a progressbar “turning off in 9 minutes and 13 seconds [-----#####]”, impossible?

My understanding of sitemaps is, that neither user-defined html elements nor js scripts are supported. So I guess number 4 is not possible!?

Here is my idea about number 3: (just a rough sketch)

rule "update timer"
when
  Time cron "0 0/1 * * * ?"
then
  if (timer != null) {
    remain = durationTimer - (now - tTimerStarted)
    postUpdate(timeRemaining, "minutes remaining: " + remain)
  }
end

This solution is not ideal because one or two additional variables have to be stored and a rule will be scheduled for every minute which could (depends on the implementation) have an influence on performance(?!?)

Long story short, my questions:

  • Is it possible to ask a timer for the remaining time directly?
  • If not, could it be worth to implement?
  • Is defining a minutely cron job rule “bad”?

i have a hack for the regular script. not beautiful but it work.
you can stop the timer anytime, and it is only running when you need it.

you need an item like that:

Number test_integer
Number gui_variable

and a site map like this one:

Selection item=test_integer mappings=[0 = "Remove timer",2 = "Active timer"]
Text  item=gui_variable visibility=gui_variable>0] icon="timer"

and the rule:

import org.openhab.core.library.types.*
import org.openhab.model.script.actions.*

var Timer myTimer= null

rule "Show a timer"
when
    Item test_integer changed
then
    val org.eclipse.xtext.xbase.lib.Functions$Function2 makeTimer = [
        int myDelay,
        org.eclipse.xtext.xbase.lib.Functions$Function2 makeTimer2 |
            if(myDelay>0) {
                postUpdate(gui_variable,myDelay)
                myTimer= createTimer(now.plusSeconds(1)) [| 
                    postUpdate(gui_variable,myDelay-1)
                    makeTimer2.apply(myDelay-1,makeTimer2)
                ]
            }else {
                //timer reach zero
                postUpdate(test_integer,0)
            }
    ]

    if(myTimer!=null) {
        myTimer.cancel
        myTimer= null
        postUpdate(gui_variable,0)
    }
    
    if(test_integer.state==2){
        //start the timer
        makeTimer.apply(60,makeTimer)
    }

end

2 Likes

I have updated my groove script for EnergySaving, now with optional timer on the gui.
It give the timer in minute if more then 1 minute, and count down in second after that.

EvergySaving.groovy

import org.openhab.core.jsr223.internal.shared.*
import org.openhab.core.items.ItemRegistry
import org.joda.time.DateTime
import org.openhab.core.library.types.*
import java.util.timer.*
import java.util.concurrent.locks.ReentrantLock

//this will turn off a light after x minutes, multiple light can be control with the same script.
//Pressing ON again on the switch will reset the timer
//the timer can be display to the gui when timeShow is specify
class EvergySavingImpl implements Rule {
    //var that take your configuration, see init in EvergySavingImpl()
    static switchToControl = []
    static ItemRegistry itemRegistry
    static Class pe
    static java.util.concurrent.locks.ReentrantLock lock  = new java.util.concurrent.locks.ReentrantLock()
    def logger = Openhab.getLogger('EvergySaving')
    
    EvergySavingImpl() {
        logger.info('EvergySaving init') 
        //You need to add your light to control here!
        //timeLimit: The light will turn off after this time in minutes
        //timerShow: give an item to show the timer on the GUI. (can be empty)
        //do not modify timer
        switchToControl << [mySwitch:'Light_A',\
                            timeLimit:15, \
                            timeShow: "", \
                            timer:null ]
        switchToControl << [mySwitch:'Light_B',\
                            timeLimit:15, \
                            timeShow: "Light_B_Timer", \
                            timer:null ]
        switchToControl << [mySwitch:'Light_Bathroom', \
                            timeLimit:45, \
                            timeShow: "", \
                            timer:null ]
    }

    java.util.List<EventTrigger> getEventTrigger() {
        def myTrigger = []
        switchToControl.each {
            myTrigger << new UpdatedEventTrigger(it.mySwitch)
            myTrigger << new ChangedEventTrigger (it.mySwitch)
        }
        return myTrigger
    }

    void execute(Event event) {
        lock.lock()
        def switchIndex = returnIndex(event.getItem().getName())
        if(event.getOldState()) {
            if(convertState(event.getNewState())) {
                makeTimer(switchIndex)
            }else {
                cancelTimer(switchIndex)
            }
        }else{
            //timer need updating
            if(convertState(event.getNewState()) && switchToControl[switchIndex].timer) {
                makeTimer(switchIndex)
            }
        }
        lock.unlock()
    }
    
    def returnIndex(String theSwitch) {
        def myIndex = null
        switchToControl.eachWithIndex { item, index ->
            if(item.mySwitch == theSwitch) {
                myIndex = index
            }
        }
        return myIndex
    }
    
    void makeTimer(def switchIndex) {
        cancelTimer(switchIndex)
        def myDelay = switchToControl[switchIndex].timeLimit
        if(switchToControl[switchIndex].timeShow) {
            makeTimerGui(switchIndex, myDelay*60)
        }else{
            switchToControl[switchIndex].timer = Openhab.createTimer(new DateTime().plusMinutes(myDelay),{
                    switchToControl[switchIndex].timer = null
                    Openhab.sendCommand(getSwitch(switchIndex), OnOffType.OFF)
                } as TimerTask )
        }
    }
    
    void makeTimerGui(def switchIndex, int time) {
        setTimerGui(switchIndex, time)
        def newTimerTime = time>60 ? 60 : 1
        switchToControl[switchIndex].timer = Openhab.createTimer(new DateTime().plusSeconds(newTimerTime),{
                if(time-1 == 0) {
                    switchToControl[switchIndex].timer = null
                    setTimerGui(switchIndex, 0)
                    Openhab.sendCommand(getSwitch(switchIndex), OnOffType.OFF)
                }else{
                    makeTimerGui(switchIndex,time-newTimerTime)
                }
            } as TimerTask )
    }
    
    void cancelTimer(def switchIndex) {
        switchToControl[switchIndex].timer?.cancel()
        switchToControl[switchIndex].timer = null
        if(switchToControl[switchIndex].timeShow!="") {
            setTimerGui(switchIndex, 0)
        }
    }
    
    void setTimerGui(def switchIndex, int time) {
        def text = ""
        if(time>60){
            text = time/60 + " minutes"
        }else{
            text = time>0 ? time + " seconds" : "0"
        }
        Openhab.sendCommand(getItem(switchToControl[switchIndex].timeShow), text)
    }
    
    def getSwitch(int index) {
        return itemRegistry.getItem(switchToControl[index].mySwitch)
    }
    
    def getItem(String name) {
        itemRegistry.getItem(name)
    }
    
    Boolean convertState(OnOffType status) {
        return (status == OnOffType.ON) ? true : false
    }
}

RuleSet getRules() {
    return new RuleSet(new EvergySavingImpl())
}
 
EvergySavingImpl.itemRegistry = this.ItemRegistry
EvergySavingImpl.pe = this.PersistenceExtensions 
2 Likes

This looks quite interesting. I don’t know if something like this was what I was asking for though. I was looking for an easier more direct way to accomplish a simple show remainder element and while your solution is clever, it’s not really anything I can give to my brother without telling him to never touch it. :wink:
I will try it in the weekend and get back to you. Thanks.

I think your main lesson here is that there is no easy nor direct way to accomplish what you are asking.

Thanks for this. Hacky or not, it is very useful.

Though I did opt for now.plusMinutes to save a few processes.

I now use it with a selection menu, where the value of the selection is used as the timer.

I signed up to the crowdsourced wifi service fon which allows you to use any Fon hotspot around the world for free. Providing you share a little bit of yours. They give you a little router to plug into your own. All secured from your local network.

I explicitly asked about where the traffic (the external ip) from someone using my hotspot would appear to be coming from. I was told it would be linked to the account of the member using it with no link to my ip. I was doubtful that it was even possible to route to another IP and I questioned it several times to be sure.

However when I plugged in and tested it, the external IP is the same IP as if I was using the local network.

When I contacted to complain, they said not to worry, the IP is of my ISP not my computer, the internal IPs are safe. I said that I would happily write 192.168.1.123 on the wall because its useless. The external IP is the one that is the problem. Tisk.

So now I only share at random times every now and then to still keep the service.

items

Number Fon_Timer_Selector "Timer duration"
Number Fon_Timer_Remaining "Remaining minutes [%s]" 

// additional items so I can see the start/stop times on the sitemap
DateTime Fon_Timer_DateTime_Start  "Last Start/Update [%1$tA, %1$tm/%1$td, %1$tI:%1$tM %1$tp]"    
DateTime Fon_Timer_DateTime_Stop  "Last Stopped [%1$tA, %1$tm/%1$td, %1$tI:%1$tM %1$tp]" 

sitemap

Frame label="Fon Switches" {
  Selection item=Fon_Timer_Selector icon="clock" mappings=[0 = "Off",15 = "15 minutes",30 = "30 minutes",60 = "60 minutes",120 = "120 minutes"]
  Text  item=Fon_Timer_Remaining visibility=[Fon_Timer_Selector>0] icon="clock"
  Switch item=Socket_02 label="Fon power switch" icon="power" labelcolor=[Socket_02==ON="red"]
}
Frame label="Fon Info" {
  Text item=Fon_Timer_Remaining
  Text item=Fon_Timer_DateTime_Start
  Text item=Fon_Timer_DateTime_Stop
}

rules


import org.openhab.core.library.types.*
import org.openhab.model.script.actions.*

import org.joda.time.*

var Timer timerFon = null

// As a precaution in case I restarted the Pi or there is a power cut, an OFF command is send at startup to the RFXcom switch 
// This is because the timer would not exist after a restart or power cut and the switch would never receive the intended  OFF command

rule "Initialize Fon switches on startup"
when
    System started
then
    sendCommand(Socket_02, OFF)
    postUpdate(Fon_Timer_Selector,0)
end


// Start a timer using the value of the selection item in the UI

rule "Turn Fon on for selected time"
when
    Item Fon_Timer_Selector changed
then
    logInfo("Fon_Timer_Selector", "changed")

    val org.eclipse.xtext.xbase.lib.Functions$Function2 makeTimer = [
        int myDelay,
        org.eclipse.xtext.xbase.lib.Functions$Function2 makeTimer2 |
            if(myDelay>0) {
                postUpdate(Fon_Timer_Remaining, myDelay)
                            // to save processes I only update UI once a minute
                timerFon=createTimer(now.plusMinutes(1)) [|
                postUpdate(Fon_Timer_Remaining, myDelay-1)
                makeTimer2.apply(myDelay-1, makeTimer2)
                ]
            }else {
                // timer reach zero
                postUpdate(Fon_Timer_Selector,0)

                // what I want to happen when the timer stops
                sendCommand(Socket_02, OFF)
                postUpdate(Fon_Timer_DateTime_Stop, new DateTimeType())
            }
    ]

    // if the timer already exists, it will be canceled and replaced with a new one 
    if(timerFon!=null) {
        timerFon.cancel
        timerFon=null
        postUpdate(Fon_Timer_Remaining,0)
    }

    // extracts the value of the selection menu so you can set different times through the UI
    var int int_Fon_Selection = (Fon_Timer_Selector.state as DecimalType).intValue

    // updates the sitemap item straight away otherwise you have to wait 1 minute 
    postUpdate(Fon_Timer_Remaining, int_Fon_Selection)

    if(Fon_Timer_Selector.state>=15){
        // start the timer if Fon_Timer_Selector is greater than 15 - could be any number greater than 0
      logInfo("Fon_Timer_Selector", "state = " + Fon_Timer_Selector.state.toString())
      makeTimer.apply(int_Fon_Selection,makeTimer)

        // what I want to happen at the start of the timer
      sendCommand(Socket_02, ON)
      postUpdate(Fon_Timer_DateTime_Start, new DateTimeType())
    }


end


1 Like

Great! I am glad ive been of use :slightly_smiling:

Your fun router must use a VPN service, you have a lots of web site that will tell you what is your external IP address. If you go to one of them with your computer connected to the FUn service, it will give you an ip address that is not your personal router external ip. You just have to test it with FUN and without it. I am sure that’s what they are doing.

Sadly not. It doesn’t use a VPN.

Though it would have been good if it did, but it’s definitely the same external IP. That’s why I don’t want it on all the time :slightly_smiling:

Just an hour here and there when i need to use one of the other Fon hotspots.

I trust it in terms of security as it is a subnet on my local network. I was more concerned with people using it for stuff they shouldn’t and it appearing to be from me. A friend who ran a guest house had every device taken by the police in a raid because of an email sent from one of his guests using the "free wifi"provided in the rooms. Took over a year to sort out.