ccutrer / openhab-jrubyscripting

JRuby Libraries for Openhab
Eclipse Public License 2.0
1 stars 1 forks source link

timed command on_expire #133

Open jimtng opened 1 year ago

jimtng commented 1 year ago

I'd like to clarify the behaviour of timed command's on_expire and I've written the following documentation to describe it. It will be in a separate PR to #130 because that is a fix for a separate issue (when on_expire isn't used) which is affecting my existing scripts.

jimtng commented 1 year ago

Timed Command

Extensions for {Item} to implement timed commands

All items have an implicit timer associated with them, enabling to easily set an item into a specific state for a specified duration and then at the expiration of that duration have the item automatically change to another state, which shall be referred to as the final state.

Timed commands are initiated by using the for: argument with the command. This is available on both the {Core::Items::GenericItem#command GenericItem#command} method and any command-specific methods, e.g. {SwitchItem#on SwitchItem#on}.

The Final State

By default, the timed command will command (or update) the item to its prior state when the timed command was issued. As a special case for {SwitchItem}, it will receive a command to revert / toggle its state, i.e. from {ON} to {OFF} and vice versa.

This can be overridden by providing an on_expire: argument or a block.

Automatic Cancellation

The timer will be cancelled, and the item's state will not be changed to the final state if:

For example, if you have a Switch on a timer and another rule sends a command to that item, even when it's commanded to the same state, the timer will be automatically canceled.

Reentrancy

These timed commands are reentrant, meaning if the same timed command is issued while an outstanding timed command exists, that timed command will be rescheduled rather than creating a new timed command. This also means that the default final state, i.e. the state the item reverts to, is the original final state prior to the start of the first timed command, unless the subsequent timed command overrides it with an on_expire argument or a block.

However, if a different timed command is issued, the previous timed command will be cancelled, and a new timed command will be created. In this case the default final state when not explicitly specified, will be the current state. Note that in the case of {SwitchItem SwitchItem}, it will use the inverse of the given command.

Sending a different duration (for:) value for the timed command will reschedule the timed command for that new duration.

@example Simple timed command with a default final state

NumberItem1.update(10) # Set the initial state
NumberItem1.command(20, for: 3.seconds)
# after 3 seconds, NumberItem1 will receive a command `10`

@example Timed command interrupted by another command

NumberItem1.update(10) # sets the initial state
NumberItem1.command(20, for: 3.seconds)
NumberItem1.update(20) # This won't cancel the timed command
                        # because it's only an update to the same state
NumberItem1.command(20) # This cancels the timed command above because it's a command
                        # NumberItem1 will not revert to 10

@example Timed command interrupted by an update to a different state

NumberItem1.update(10) # sets the initial state
NumberItem1.command(20, for: 3.seconds)
NumberItem1.update(21) # This cancels the timed command above.
                        # NumberItem1 will not revert to 10

@example Reentrancy with default final state on a Number Item

Number1.update(10)
Number1.command(20, for: 10.seconds)
sleep 5
Number1.command(20, for: 10.seconds) # This is the same timed command.
                                    # It extends the timer, the final state is still 10

@example Reentrancy with a different command

Number1.update(10)
Number1.command(20, for: 10.seconds)
Number1.command(30, for: 10.seconds) # This is a different timed command
                                    # It cancels the previous timed command,
                                    # and the final state is now 20

@example Reentrancy with an explicit on_expire

Number1.update(10)
Number1.command(20, for: 10.seconds)
Number1.command(20, for: 5.seconds, on_expire: 15) # This cancels the previous timed command
# In 5 seconds, Number1 will receive a command `15`

@example Timed Command on a Switch Item. The final state is the inverse of the command

Switch1.on # sets the initial state, which doesn't matter here
Switch1.on for: 3.seconds # Switch1 will receive OFF command in 3 seconds

@example Reentrancy on a Switch Item

Switch1.on for: 3.seconds
Switch1.on for: 10.seconds
# Switch1 will remain ON for 10 seconds and then receive an OFF command
# unless it received another command or its state updated to anything other than ON
ccutrer commented 1 year ago

this all sounds great. good docs too