Design Pattern: Graceful Retry Actions
Problem: An actuator sometimes does not respond to commands (e.g., MQTT device offline).
Standard rules then produce error messages or get stuck.
Solution:
- Define a rule that sends commands with a retry mechanism.
- Use timers to control repetitions.
- After a configurable timeout, an alternative action (e.g., notification) is triggered.
Advantages:
- Robust handling of network issues or unstable devices.
- Decouples error handling from normal workflow.
1. Concept
Goal: A command should be sent to a device. If the device does not respond (e.g., offline), we retry the command until either:
- the device responds, or
- a maximum number of attempts / timeout is reached.
Optionally, an alternative action can occur after the timeout (e.g., notification, logging, alarm).
2. Implementation in Rule DSL
// Example: Turn on light with retry
var Timer retryTimer = null
var int retryCount = 0
var final int MAX_RETRIES = 3
var final int RETRY_INTERVAL = 10 // seconds
rule "Graceful Retry LightSwitch"
when
Item SomeTrigger changed to ON
then
retryCount = 0
// Function to send command with retry
retryTimer = createTimer(now.plusSeconds(0), [ |
try {
// Try sending the command
LightSwitch.sendCommand(ON)
logInfo("Retry", "Command sent successfully!")
if(retryTimer !== null) {
retryTimer.cancel()
}
} catch(Throwable t) {
retryCount = retryCount + 1
logWarn("Retry", "Error sending command, attempt #" + retryCount)
if(retryCount < MAX_RETRIES) {
// Reschedule timer
retryTimer.reschedule(now.plusSeconds(RETRY_INTERVAL))
} else {
logError("Retry", "Maximum number of attempts reached!")
// Alternative action, e.g., notification
sendNotification("admin@example.com", "LightSwitch could not be turned on")
}
}
])
end
DSL Explanation:
createTimercreates a timer to execute the retry function.try/catchcaptures errors when sending the command.reschedulesets the timer again if further attempts are allowed.MAX_RETRIESandRETRY_INTERVALare configurable.- After reaching maximum retries, an alternative action is executed.
3. Implementation in Python (Jython)
from core.rules import rule
from core.triggers import when
from core.log import logging
import threading
log = logging.getLogger("GracefulRetry")
MAX_RETRIES = 3
RETRY_INTERVAL = 10 # seconds
def send_command_with_retry(item_name, command, retries=0):
try:
events.sendCommand(item_name, command)
log.info(f"Command '{command}' sent to {item_name} successfully!")
except Exception as e:
retries += 1
log.warn(f"Failed to send command to {item_name}, attempt #{retries}")
if retries < MAX_RETRIES:
threading.Timer(RETRY_INTERVAL, send_command_with_retry, [item_name, command, retries]).start()
else:
log.error(f"Max retries reached for {item_name}. Performing alternative action.")
events.sendCommand("NotificationItem", f"{item_name} could not be switched ON!")
@rule("Graceful Retry Action Python")
@when("Item SomeTrigger changed to ON")
def graceful_retry_action(event):
send_command_with_retry("LightSwitch", "ON")
Python Explanation:
- Recursive function
send_command_with_retrysends the command and retries on error. threading.Timerschedules a retry afterRETRY_INTERVALseconds.- Maximum attempts are limited by
MAX_RETRIES. - Alternative actions can include notifications or logging.
Advantages of this Implementation
- Robust handling of unstable devices or temporary network issues.
- Rules do not block normal workflow.
- Flexible configuration of interval, number of attempts, and alternative actions.
Graceful Retry Pattern with Exponential Backoff
1. Rule DSL with Exponential Backoff
var Timer retryTimer = null
var int retryCount = 0
var final int MAX_RETRIES = 5
var int retryInterval = 5 // initial interval in seconds
var final int MAX_INTERVAL = 60 // max interval in seconds
rule "Graceful Retry LightSwitch with Backoff"
when
Item SomeTrigger changed to ON
then
retryCount = 0
retryInterval = 5
retryTimer = createTimer(now.plusSeconds(0), [ |
try {
LightSwitch.sendCommand(ON)
logInfo("RetryBackoff", "Command successfully sent!")
if(retryTimer !== null) retryTimer.cancel()
} catch(Throwable t) {
retryCount = retryCount + 1
logWarn("RetryBackoff", "Failed attempt #" + retryCount)
if(retryCount < MAX_RETRIES) {
retryInterval = Math.min(retryInterval * 2, MAX_INTERVAL)
logInfo("RetryBackoff", "Next attempt in " + retryInterval + " seconds")
retryTimer.reschedule(now.plusSeconds(retryInterval))
} else {
logError("RetryBackoff", "Max retries reached!")
sendNotification("admin@example.com", "LightSwitch could not be switched ON!")
}
}
])
end
Explanation:
- Interval doubles on each failure (
retryInterval * 2) untilMAX_INTERVAL. MAX_RETRIESlimits the number of attempts.rescheduleuses the current interval.
2. Python (Jython) with Exponential Backoff
from core.rules import rule
from core.triggers import when
from core.log import logging
import threading
log = logging.getLogger("GracefulRetryBackoff")
MAX_RETRIES = 5
INITIAL_INTERVAL = 5 # seconds
MAX_INTERVAL = 60 # seconds
def send_command_with_backoff(item_name, command, retries=0, interval=INITIAL_INTERVAL):
try:
events.sendCommand(item_name, command)
log.info(f"Command '{command}' sent to {item_name} successfully!")
except Exception as e:
retries += 1
log.warn(f"Attempt #{retries} failed for {item_name}: {e}")
if retries < MAX_RETRIES:
next_interval = min(interval * 2, MAX_INTERVAL)
log.info(f"Next retry in {next_interval} seconds")
threading.Timer(next_interval, send_command_with_backoff, [item_name, command, retries, next_interval]).start()
else:
log.error(f"Max retries reached for {item_name}. Triggering alternative action.")
events.sendCommand("NotificationItem", f"{item_name} could not be switched ON!")
@rule("Graceful Retry Action Python with Backoff")
@when("Item SomeTrigger changed to ON")
def graceful_retry_backoff(event):
send_command_with_backoff("LightSwitch", "ON")
Explanation:
- Start interval:
INITIAL_INTERVAL. - After each failure, interval doubles but does not exceed
MAX_INTERVAL. - Recursive timer function automatically retries.
- Alternative action is triggered if
MAX_RETRIESis reached.
Advantages of Exponential Backoff
- Reduces network load for repeated failures.
- Gives devices more time to reconnect.
- Error handling remains elegant and decoupled from normal workflow.
- Parameters like
INITIAL_INTERVAL,MAX_INTERVAL, andMAX_RETRIESare easy to adjust.
Generic Multi-Device Retry Function
1. Rule DSL: Multi-Device Retry with Backoff
var Map<String, Timer> retryTimers = newHashMap
var Map<String, Integer> retryCounts = newHashMap
var final int MAX_RETRIES = 5
var final int INITIAL_INTERVAL = 5 // seconds
var final int MAX_INTERVAL = 60 // seconds
rule "Graceful Retry Multiple Devices"
when
Item RetryTrigger changed to ON
then
val devices = newArrayList("LightSwitch", "HeaterSwitch", "FanSwitch")
devices.forEach [ device |
retryCounts.put(device, 0)
val int initialInterval = INITIAL_INTERVAL
val Timer t = createTimer(now.plusSeconds(0), [ |
try {
events.sendCommand(device, ON)
logInfo("MultiRetry", device + " command sent successfully!")
retryTimers.get(device)?.cancel()
} catch(Throwable t) {
val int count = retryCounts.get(device) + 1
retryCounts.put(device, count)
logWarn("MultiRetry", device + " attempt #" + count + " failed.")
if(count < MAX_RETRIES) {
val int nextInterval = Math.min(initialInterval * (2^count), MAX_INTERVAL)
logInfo("MultiRetry", "Next attempt for " + device + " in " + nextInterval + " seconds")
retryTimers.get(device)?.reschedule(now.plusSeconds(nextInterval))
} else {
logError("MultiRetry", device + " max retries reached!")
sendNotification("admin@example.com", device + " could not be switched ON!")
}
}
])
retryTimers.put(device, t)
]
end
Explanation:
devicescontains the list of actuators to retry.- Each actuator gets its own timer and retry count.
- Exponential backoff:
nextInterval = initialInterval * (2^count)up toMAX_INTERVAL. - Alternative action (notification) triggered at max retries.
2. Python (Jython): Multi-Device Retry with Backoff
from core.rules import rule
from core.triggers import when
from core.log import logging
import threading
log = logging.getLogger("MultiDeviceRetryBackoff")
MAX_RETRIES = 5
INITIAL_INTERVAL = 5 # seconds
MAX_INTERVAL = 60 # seconds
def send_command_with_backoff(item_name, command, retries=0, interval=INITIAL_INTERVAL):
try:
events.sendCommand(item_name, command)
log.info(f"Command '{command}' sent to {item_name} successfully!")
except Exception as e:
retries += 1
log.warn(f"Attempt #{retries} failed for {item_name}: {e}")
if retries < MAX_RETRIES:
next_interval = min(interval * 2, MAX_INTERVAL)
log.info(f"Next retry for {item_name} in {next_interval} seconds")
threading.Timer(next_interval, send_command_with_backoff, [item_name, command, retries, next_interval]).start()
else:
log.error(f"Max retries reached for {item_name}. Performing alternative action.")
events.sendCommand("NotificationItem", f"{item_name} could not be switched ON!")
@rule("Graceful Retry Multiple Devices Python")
@when("Item RetryTrigger changed to ON")
def multi_device_retry(event):
devices = ["LightSwitch", "HeaterSwitch", "FanSwitch"]
for device in devices:
send_command_with_backoff(device, "ON")
Explanation:
devicescontains all actuators to retry.- Recursive function handles each actuator individually.
- Exponential backoff applied automatically.
- Alternative actions are triggered on max retries.
Advantages of Multi-Device Version
- Scalable for any number of devices.
- Error handling remains decoupled from normal workflow.
- Exponential backoff reduces network load on repeated failures.
- Easy configuration:
MAX_RETRIES,INITIAL_INTERVAL,MAX_INTERVAL. - Retry function can be easily adapted per device (different intervals or alternative actions).
Multi-Device Retry Pattern with Online Check
1. Rule DSL: Multi-Device Retry with Online Check
var Map<String, Timer> retryTimers = newHashMap
var Map<String, Integer> retryCounts = newHashMap
var final int MAX_RETRIES = 5
var final int INITIAL_INTERVAL = 5 // seconds
var final int MAX_INTERVAL = 60 // seconds
rule "Graceful Retry Multiple Devices with Online Check"
when
Item RetryTrigger changed to ON
then
val devices = newArrayList("LightSwitch", "HeaterSwitch", "FanSwitch")
devices.forEach [ device |
retryCounts.put(device, 0)
val int initialInterval = INITIAL_INTERVAL
val Timer t = createTimer(now.plusSeconds(0), [ |
try {
// Online check: device is reachable only if it has a valid state
if(items.get(device) !== NULL) {
events.sendCommand(device, ON)
logInfo("MultiRetryOnline", device + " command sent successfully!")
retryTimers.get(device)?.cancel()
} else {
throw new IllegalStateException("Device " + device + " is offline")
}
} catch(Throwable t) {
val int count = retryCounts.get(device) + 1
retryCounts.put(device, count)
logWarn("MultiRetryOnline", device + " attempt #" + count + " failed: " + t.message)
if(count < MAX_RETRIES) {
val int nextInterval = Math.min(initialInterval * (2^count), MAX_INTERVAL)
logInfo("MultiRetryOnline", "Next attempt for " + device + " in " + nextInterval + " seconds")
retryTimers.get(device)?.reschedule(now.plusSeconds(nextInterval))
} else {
logError("MultiRetryOnline", device + " max retries reached!")
sendNotification("admin@example.com", device + " could not be switched ON!")
}
}
])
retryTimers.put(device, t)
]
end
DSL Explanation:
items.get(device) !== NULLchecks if the actuator is online (alternatively,DeviceStatusitems could be used).- Offline errors trigger a retry.
- Exponential backoff is applied as before.
2. Python (Jython): Multi-Device Retry with Online Check
from core.rules import rule
from core.triggers import when
from core.log import logging
import threading
log = logging.getLogger("MultiDeviceRetryOnline")
MAX_RETRIES = 5
INITIAL_INTERVAL = 5 # seconds
MAX_INTERVAL = 60 # seconds
def send_command_with_online_check(item_name, command, retries=0, interval=INITIAL_INTERVAL):
try:
# Online check: verify the item has a valid state
if str(items[item_name]) != "NULL":
events.sendCommand(item_name, command)
log.info(f"Command '{command}' sent to {item_name} successfully!")
else:
raise Exception(f"{item_name} is offline")
except Exception as e:
retries += 1
log.warn(f"Attempt #{retries} failed for {item_name}: {e}")
if retries < MAX_RETRIES:
next_interval = min(interval * 2, MAX_INTERVAL)
log.info(f"Next retry for {item_name} in {next_interval} seconds")
threading.Timer(next_interval, send_command_with_online_check, [item_name, command, retries, next_interval]).start()
else:
log.error(f"Max retries reached for {item_name}. Performing alternative action.")
events.sendCommand("NotificationItem", f"{item_name} could not be switched ON!")
@rule("Graceful Retry Multiple Devices Python with Online Check")
@when("Item RetryTrigger changed to ON")
def multi_device_retry_online(event):
devices = ["LightSwitch", "HeaterSwitch", "FanSwitch"]
for device in devices:
send_command_with_online_check(device, "ON")
Python Explanation:
str(items[item_name]) != "NULL"checks whether the actuator has a valid state.- If the device is offline, the retry mechanism is triggered.
- Exponential backoff reduces unnecessary retries.
- Max retries trigger an alternative action (e.g., notification).
Advantages of Online-Check Version
- Avoids unnecessary retry attempts for offline devices.
- Saves network traffic and reduces actuator load.
- Works for any number of devices simultaneously.
- Exponential backoff ensures robust and flexible retries.
- Alternative actions reliably notify if devices remain offline.
Dynamic Multi-Device Retry with Online Check and Exponential Backoff
Advantage: You no longer need to manually maintain a device list—the rule dynamically retrieves actuators from a group or a configured item list.
1. Rule DSL: Dynamic Devices with Retry, Backoff, and Online Check
var Map<String, Timer> retryTimers = newHashMap
var Map<String, Integer> retryCounts = newHashMap
var final int MAX_RETRIES = 5
var final int INITIAL_INTERVAL = 5 // seconds
var final int MAX_INTERVAL = 60 // seconds
rule "Dynamic Multi-Device Retry with Backoff and Online Check"
when
Item RetryTrigger changed to ON
then
// Dynamically fetch all devices from a group
val devices = GroupDevices.members.filter[i | i !== NULL].map[i | i.name]
devices.forEach [ device |
retryCounts.put(device, 0)
val int initialInterval = INITIAL_INTERVAL
val Timer t = createTimer(now.plusSeconds(0), [ |
try {
// Online check: item must have a valid state
if(items.get(device) !== NULL) {
events.sendCommand(device, ON)
logInfo("DynamicRetry", device + " command sent successfully!")
retryTimers.get(device)?.cancel()
} else {
throw new IllegalStateException("Device " + device + " is offline")
}
} catch(Throwable t) {
val int count = retryCounts.get(device) + 1
retryCounts.put(device, count)
logWarn("DynamicRetry", device + " attempt #" + count + " failed: " + t.message)
if(count < MAX_RETRIES) {
val int nextInterval = Math.min(initialInterval * (2^count), MAX_INTERVAL)
logInfo("DynamicRetry", "Next attempt for " + device + " in " + nextInterval + " seconds")
retryTimers.get(device)?.reschedule(now.plusSeconds(nextInterval))
} else {
logError("DynamicRetry", device + " max retries reached!")
sendNotification("admin@example.com", device + " could not be switched ON!")
}
}
])
retryTimers.put(device, t)
]
end
DSL Explanation:
GroupDevicesis an openHAB group containing all retry-capable actuators.members.filter[i | i !== NULL].map[i | i.name]generates a list of active devices.- Exponential backoff is calculated individually per device.
- Alternative action (notification) is triggered on max retries.
2. Python (Jython): Dynamic Devices with Retry, Backoff, and Online Check
from core.rules import rule
from core.triggers import when
from core.log import logging
import threading
log = logging.getLogger("DynamicMultiDeviceRetry")
MAX_RETRIES = 5
INITIAL_INTERVAL = 5 # seconds
MAX_INTERVAL = 60 # seconds
def send_command_with_online_check(item_name, command, retries=0, interval=INITIAL_INTERVAL):
try:
if str(items[item_name]) != "NULL":
events.sendCommand(item_name, command)
log.info(f"Command '{command}' sent to {item_name} successfully!")
else:
raise Exception(f"{item_name} is offline")
except Exception as e:
retries += 1
log.warn(f"Attempt #{retries} failed for {item_name}: {e}")
if retries < MAX_RETRIES:
next_interval = min(interval * 2, MAX_INTERVAL)
log.info(f"Next retry for {item_name} in {next_interval} seconds")
threading.Timer(next_interval, send_command_with_online_check,
[item_name, command, retries, next_interval]).start()
else:
log.error(f"Max retries reached for {item_name}. Performing alternative action.")
events.sendCommand("NotificationItem", f"{item_name} could not be switched ON!")
@rule("Dynamic Multi-Device Retry Python")
@when("Item RetryTrigger changed to ON")
def dynamic_multi_device_retry(event):
# Dynamically fetch all devices from an openHAB group
devices = [i.name for i in items["GroupDevices"].members if i is not None]
for device in devices:
send_command_with_online_check(device, "ON")
Python Explanation:
items["GroupDevices"].membersdynamically retrieves all group members.- Each actuator is individually handled with online check and exponential backoff.
- Max retries trigger the alternative action (notification).
- No manual device list maintenance is required.
Advantages of Dynamic Multi-Device Version
- Automatic device detection via groups.
- Exponential backoff reduces unnecessary attempts.
- Online check prevents useless retries for offline devices.
- Scalable to any number of actuators.
- Alternative actions reliably notify for devices that remain offline.
Fully Configurable Template Version for openHAB (Jython)
Features offered:
- All parameters configurable via Items:
MAX_RETRIES,INITIAL_INTERVAL,MAX_INTERVAL, device group, alternative action. - Supports multi-device, online check, and exponential backoff.
- No code changes required; fully configurable via Items.
1. Python (Jython) Template Version
from core.rules import rule
from core.triggers import when
from core.log import logging
import threading
log = logging.getLogger("ConfigurableMultiDeviceRetry")
# Configuration Items in openHAB
# Number RetryMaxAttempts "Max Retries"
# Number RetryInitialInterval "Initial Interval"
# Number RetryMaxInterval "Max Interval"
# Group RetryDevices
# String RetryAlternativeAction
def send_command_with_online_check(item_name, command, retries=0, interval=None):
if interval is None:
interval = int(items["RetryInitialInterval"])
max_retries = int(items["RetryMaxAttempts"])
max_interval = int(items["RetryMaxInterval"])
try:
if str(items[item_name]) != "NULL":
events.sendCommand(item_name, command)
log.info(f"Command '{command}' sent to {item_name} successfully!")
else:
raise Exception(f"{item_name} is offline")
except Exception as e:
retries += 1
log.warn(f"Attempt #{retries} failed for {item_name}: {e}")
if retries < max_retries:
next_interval = min(interval * 2, max_interval)
log.info(f"Next retry for {item_name} in {next_interval} seconds")
threading.Timer(next_interval, send_command_with_online_check,
[item_name, command, retries, next_interval]).start()
else:
log.error(f"Max retries reached for {item_name}. Executing alternative action.")
alt_action = str(items["RetryAlternativeAction"])
if alt_action:
events.sendCommand("NotificationItem", f"{item_name}: {alt_action}")
else:
events.sendCommand("NotificationItem", f"{item_name} could not be switched ON!")
@rule("Configurable Multi-Device Retry")
@when("Item RetryTrigger changed to ON")
def configurable_multi_device_retry(event):
devices = [i.name for i in items["RetryDevices"].members if i is not None]
for device in devices:
send_command_with_online_check(device, "ON")
2. Explanation
RetryDevices(Group): Contains all actuators to retry. Evaluated dynamically.RetryMaxAttempts(Number): Maximum retries (e.g., 5).RetryInitialInterval(Number): Start interval in seconds (e.g., 5).RetryMaxInterval(Number): Maximum allowed interval (e.g., 60).RetryAlternativeAction(String): Message or action executed on max retries.- Exponential Backoff: Interval doubles after each failure until
RetryMaxInterval. - Online Check: Verifies item has a valid state before sending command.
- Multi-Device: All devices in the group are handled concurrently.
3. Advantages
- Fully configurable without changing code.
- Supports any number of devices dynamically.
- Robust handling of unstable devices or network issues.
- Exponential backoff reduces unnecessary retries.
- Alternative actions reliably notify on failed commands.
Rule DSL Version as Fully Configurable Template for openHAB
1. Rule DSL: Configurable Multi-Device Retry Template
var Map<String, Timer> retryTimers = newHashMap
var Map<String, Integer> retryCounts = newHashMap
rule "Configurable Multi-Device Retry"
when
Item RetryTrigger changed to ON
then
// Read configuration from Items
val int MAX_RETRIES = RetryMaxAttempts.state as Number
val int INITIAL_INTERVAL = RetryInitialInterval.state as Number
val int MAX_INTERVAL = RetryMaxInterval.state as Number
val String ALT_ACTION = RetryAlternativeAction.state.toString
// Dynamically fetch all devices from group
val devices = RetryDevices.members.filter[i | i !== NULL].map[i | i.name]
devices.forEach [ device |
retryCounts.put(device, 0)
val int startInterval = INITIAL_INTERVAL
val Timer t = createTimer(now.plusSeconds(0), [ |
try {
// Online check: item must have a valid state
if(items.get(device) !== NULL) {
events.sendCommand(device, ON)
logInfo("DynamicRetryDSL", device + " command sent successfully!")
retryTimers.get(device)?.cancel()
} else {
throw new IllegalStateException("Device " + device + " is offline")
}
} catch(Throwable ex) {
val int count = retryCounts.get(device) + 1
retryCounts.put(device, count)
logWarn("DynamicRetryDSL", device + " attempt #" + count + " failed: " + ex.message)
if(count < MAX_RETRIES) {
// Exponential backoff
val int nextInterval = Math.min(startInterval * (2^count), MAX_INTERVAL)
logInfo("DynamicRetryDSL", "Next attempt for " + device + " in " + nextInterval + " seconds")
retryTimers.get(device)?.reschedule(now.plusSeconds(nextInterval))
} else {
logError("DynamicRetryDSL", device + " max retries reached!")
if(ALT_ACTION != "") {
sendNotification("admin@example.com", device + ": " + ALT_ACTION)
} else {
sendNotification("admin@example.com", device + " could not be switched ON!")
}
}
}
])
retryTimers.put(device, t)
]
end
2. Configuration Items
| Item | Type | Purpose |
|---|---|---|
RetryTrigger |
Switch | Trigger for retry event |
RetryDevices |
Group | Contains all devices to retry |
RetryMaxAttempts |
Number | Maximum retry attempts |
RetryInitialInterval |
Number | Start interval for retry (seconds) |
RetryMaxInterval |
Number | Maximum allowed interval (seconds) |
RetryAlternativeAction |
String | Message/action on max retries (e.g., notification) |
3. Advantages
- Fully configurable via Items; no code changes needed.
- Supports any number of devices in a group.
- Exponential backoff reduces unnecessary retries.
- Online check prevents useless retries for offline devices.
- Alternative actions reliably notify if devices remain offline.
Step-by-Step Guide: Configurable Multi-Device Retry in openHAB
1. Create required Items
You need Items for:
- Trigger
- Device group
- Retry parameters
- Alternative action
1.1 Trigger Item
Switch RetryTrigger "Retry Trigger"
- Set to
ONto start the retry process.
1.2 Device Group
Group RetryDevices "Devices to Retry"
- Collect all actuators to retry in this group.
- Example:
Switch LightSwitch "Living Room Light" (RetryDevices)
Switch HeaterSwitch "Heater" (RetryDevices)
Switch FanSwitch "Fan" (RetryDevices)
1.3 Retry Parameters
Number RetryMaxAttempts "Max Retries" <number>
Number RetryInitialInterval "Initial Interval (s)" <time>
Number RetryMaxInterval "Max Interval (s)" <time>
String RetryAlternativeAction "Alternative Action"
RetryMaxAttemptse.g., 5RetryInitialIntervale.g., 5 secondsRetryMaxIntervale.g., 60 secondsRetryAlternativeActione.g.,"Notify admin"
2. Python (Jython) Version
- Save the Python template code in
conf/automation/jsr223/python/. - Example filename:
multi_device_retry.py - Content: see Python Template above.
Test:
- Turn
RetryTriggerON - Observe logs (
openhab.log) and notifications (NotificationItem)
3. Rule DSL Version
- Save the DSL template code in
conf/automation/rules/. - Example filename:
multi_device_retry.rules - Content: see DSL Template above.
Test:
- Turn
RetryTriggerON - Check logs and notifications
4. Adjust Parameters
| Item | Purpose |
|---|---|
RetryMaxAttempts |
Number of retries per device |
RetryInitialInterval |
Start interval for retry in seconds |
RetryMaxInterval |
Maximum interval for exponential backoff |
RetryAlternativeAction |
Message on max retries (e.g., "Notify admin") |
- Adjust directly via Items; no code modification required.
5. Extension Possibilities
- Device-specific intervals: Create additional Items or maps for different retry parameters per device.
- Conditional retry: Check status items to retry only specific devices.
- Logging & monitoring: Use dashboard or notifications to immediately identify failed commands.
6. Advantages
- Fully dynamic and configurable.
- Supports any number of devices simultaneously.
- Online check prevents unnecessary retries.
- Exponential backoff reduces network load.
- Alternative actions reliably notify for devices permanently offline.
