How to restart binding with in rules?

As of openHAB 2.5 M5 you need to issue the SSH key to access the Karaf console, e.g. for restarting a binding.

The following post provides some explanation that made it work on my setup:

Here’s a JSR223 Jython watchdog script I wrote to monitor binding status and automatically restart a binding if needed (through the helper method schedule_binding_restart()):

from core.rules import rule
from core.triggers import when
from org.eclipse.smarthome.core.thing import ThingUID

from core.actions import LogAction
from core.actions import Telegram
from core.actions import Exec
from core.actions import ScriptExecution

from org.joda.time import DateTime

import pprint

pp = pprint.PrettyPrinter(indent=4)

rule_init_timestamp = DateTime.now()
logTitle = "watchdog.py@{ts}".format(ts=rule_init_timestamp.toString("HH:mm:ss"))
ruleTimeStamp = " -- (Rule set initialised {date} at {ts})".format(
    date=rule_init_timestamp.toString("E d MMM yyyy"),
    ts=rule_init_timestamp.toString("HH:mm:ss (z)"),
)
rulePrefix = "Watchdog | "


# Binding restart timers:
timers = {}

# Binding restart counters:
binding_restarts = {}


def schedule_binding_restart(
    binding_id,
    binding_name,
    binding_thing_name,
    delay_seconds,
    reschedule_timer_on_update=False,
    notify_restart=False,
):
    """Schedule a binding restart if needed (if the Thing status differs from 'ONLINE')
    
    Arguments:
        binding_id {String} -- The binding identifier, e.g. 'org.openhab.binding.xyzzy'
        binding_name {String} -- The name of the binding, e.g. 'XYZZY'
        binding_thing_name {String} -- The Thing name linked to the binding, e.g. 'binding:id:x:y:z'
        delay_seconds {Integer} -- Number of seconds to wait before restarting the binding
    
    Keyword Arguments:
        reschedule_timer_on_update {bool} -- If a status update is received, reschedule the restart timer (default = False)
        notify_restart {bool} -- Issue a notification if the binding is scheduled for restarting (default = False)
    """
    global timers
    global binding_restarts

    if delay_seconds < 0:
        delay_seconds = 0

    current_state = str(things.get(ThingUID(binding_thing_name)).status)
    if current_state == "ONLINE":
        if timers.get(binding_id):
            timers[binding_id].cancel()
            timers[binding_id] = None
        return

    if timers.get(binding_id) is None:
        if notify_restart is True:
            Telegram.sendTelegram(
                "homebrainz",
                u"<b>Automatic binding restart scheduled</b> for the {binding_name} binding in {delay_seconds} seconds (current status of binding ID '{binding_id}' is '{state}')".format(
                    binding_id=binding_id,
                    binding_name=binding_name,
                    delay_seconds=delay_seconds,
                    state=current_state,
                ),
            )
        # Define the call-back that will be executed when the timer expires
        def cb():
            global logTitle
            current_state = str(things.get(ThingUID(binding_thing_name)).status)
            if current_state == "ONLINE":
                LogAction.logInfo(
                    logTitle,
                    u"No need to restart the {binding_name} binding (Thing UID = '{thing_uid}', current status is '{state}').".format(
                        binding_name=binding_name,
                        thing_uid=binding_thing_name,
                        state=current_state,
                    ),
                )
                if notify_restart is True:
                    Telegram.sendTelegram(
                        "homebrainz",
                        u"<b>Automatic binding restart cancelled</b> for the {binding_name} binding (Thing UID = '{thing_uid}', current status is '{state}')".format(
                            binding_name=binding_name,
                            thing_uid=binding_thing_name,
                            state=current_state,
                        ),
                    )
            else:
                LogAction.logInfo(
                    logTitle,
                    u"Will now restart the {binding_name} binding (Thing UID = '{thing_uid}', current status is '{state}').".format(
                        binding_name=binding_name,
                        thing_uid=binding_thing_name,
                        state=current_state,
                    ),
                )

                # Keep track of binding restarts
                restart_counter = binding_restarts.get(binding_id)
                if restart_counter is None:
                    binding_restarts[binding_id] = 1
                else:
                    binding_restarts[binding_id] = int(restart_counter) + 1

                if notify_restart is True:
                    Telegram.sendTelegram(
                        "homebrainz",
                        u"<b>Automatic binding restart</b> of the {binding_name} binding (Thing UID = '{thing_uid}', current status is '{state}')".format(
                            binding_name=binding_name,
                            thing_uid=binding_thing_name,
                            state=current_state,
                        ),
                    )
                # Restart the binding
                Exec.executeCommandLine(
                    "/bin/sh@@-c@@ssh -p 8101 -l openhab localhost -i ~/.ssh/karaf_openhab_id.rsa 'bundle:restart {binding_id}'".format(
                        binding_id=binding_id
                    )
                )
            timers[binding_id] = None

        timers[binding_id] = ScriptExecution.createTimer(
            DateTime.now().plusSeconds(delay_seconds), cb
        )

    else:
        if reschedule_timer_on_update is True:
            LogAction.logInfo(
                logTitle,
                u"Will now reschedule the {binding_name} binding restart timer (Thing UID = '{thing_uid}', current status is '{state}').".format(
                    binding_name=binding_name,
                    thing_uid=binding_thing_name,
                    state=current_state,
                ),
            )
            timers.get(binding_id).reschedule(DateTime.now().plusSeconds(delay_seconds))


@rule(
    rulePrefix + "Key thing status update",
    description=u"""This rule will update Thing-based proxy items to
report the status of key Things (e.g., Z-Wave nodes, IKEA TRÅDFRI gateway).
Please note that every Thing must be defined in keyThingsDict
and have its own @when() decorator for the rule to work."""
    + ruleTimeStamp,
    tags=["watchdog", ruleTimeStamp],
)
@when("System started")
@when("Time cron 0 0/10 * * * ?")
# Z-Wave controller (ZME E UZB1)
@when("Thing zwave:serial_zstick:controller changed")
# Fibaro Roller Shutter 3 (first floor betdrooms)
@when("Thing zwave:device:controller:node2 changed")
@when("Thing zwave:device:controller:node3 changed")
@when("Thing zwave:device:controller:node4 changed")
# Fakro ARZ Z-Wave (attic roof shutters)
@when("Thing zwave:device:controller:node5 changed")
@when("Thing zwave:device:controller:node6 changed")
@when("Thing zwave:device:controller:node7 changed")
# Fakro ZWP-10 portable controllers
@when("Thing zwave:device:controller:node8 changed")
@when("Thing zwave:device:controller:node9 changed")
@when("Thing zwave:device:controller:node10 changed")
# IKEA TRÅDFRI gateway:
@when("Thing tradfri:gateway:gwa1b2c3d4e5f6 changed")
# Wx OWM status:
@when("Thing openweathermap:weather-api:abcd1234 changed")
@when("Thing openweathermap:weather-and-forecast:abcd1234:local changed")
# Buienradar:
@when("Thing buienradar:rain_forecast:home changed")
def KeyThingStatusUpdate(event):
    global logTitle
    keyThingsDict = {
        "zwave:serial_zstick:controller": "ZWave_Controller_Status",
        "zwave:device:controller:node2": "Bedroom_NE_Shutter_Status",
        "zwave:device:controller:node3": "Bedroom_SE_Shutter_Status",
        "zwave:device:controller:node4": "Bedroom_SW_Shutter_Status",
        "zwave:device:controller:node5": "Attic_Shutter_NE_Status",
        "zwave:device:controller:node6": "Attic_Shutter_SE_Status",
        "zwave:device:controller:node7": "Attic_Shutter_SW_Status",
        "zwave:device:controller:node8": "ZWave_Remote_ZWP10_1_Status",
        "zwave:device:controller:node9": "ZWave_Remote_ZWP10_2_Status",
        "zwave:device:controller:node10": "ZWave_Remote_ZWP10_3_Status",
        # IKEA TRÅDFRI:
        "tradfri:gateway:gwa1b2c3d4e5f6": "IKEA_TRADFRI_Gateway_Status",
        # OWM Binding:
        "openweathermap:weather-api:abcd1234": "Wx_OWM_API_Status",
        "openweathermap:weather-and-forecast:abcd1234:local": "Wx_OWM_Weather_Status",
        # BuienRadar binding:
        "buienradar:rain_forecast:home": "BuienRadar_Status",
    }
    KeyThingStatusUpdate.log.info("event = " + pp.pformat(event))

    keyThings = []
    if event:
        keyThings.append(str(event.thingUID))
    else:
        for k in keyThingsDict.keys():
            keyThings.append(k)

    for k in keyThings:
        keyItem = keyThingsDict[k]
        nodeName = k.split(":")[-1]
        # thing state is not available in event if rule triggered by cron:
        nodeState = str(event.statusInfo) if event else things.get(ThingUID(k)).status
        KeyThingStatusUpdate.log.debug(
            "Thing '{node_name}' (item {item_name}) status changed to '{node_state}'".format(
                node_name=nodeName, item_name=keyItem, node_state=nodeState
            )
        )
        events.postUpdate(keyItem, str(nodeState))

        # Restart some bindings if needed
        if k == "tradfri:gateway:gwa1b2c3d4e5f6":
            schedule_binding_restart(
                "org.openhab.binding.tradfri",
                "IKEA TRADFRI",
                k,
                60,
                reschedule_timer_on_update=True,
            )
        elif k == "openweathermap:weather-and-forecast:abcd1234:local":
            schedule_binding_restart(
                "org.openhab.binding.openweathermap",
                "OpenWeatherMap (Current Weather and Forecast)",
                k,
                60,
            )
        elif k == "buienradar:rain_forecast:home":
            schedule_binding_restart(
                "org.openhab.binding.buienradar",
                "BuienRadar (Rain Forecast)",
                k,
                90,
                reschedule_timer_on_update=True,
                notify_restart=False,
            )


# NOTE - "when System shuts down" does not yet work. Using workaround, see:
# https://openhab-scripters.github.io/openhab-helper-libraries/Guides/Triggers.html
def scriptUnloaded():
    # call rule when this file is unloaded
    global logTitle
    logPrefix = "scriptUnloaded(): "

    LogAction.logInfo(
        logTitle,
        logPrefix + "Shutting down -- 'scriptUnloaded()' hook - cancelling all timers",
    )

    global binding_restarts
    for key, value in list(binding_restarts.items()):
        LogAction.logInfo(
            logTitle,
            logPrefix
            + u"Binding {key} restart count: {cnt}".format(key=key, cnt=value),
        )
        # binding_restarts.get(binding_id)
    global timers
    for key, timer in list(timers.items()):
        # timer = timers.get(key)
        if timer:
            LogAction.logInfo(
                logTitle, logPrefix + u"Cancelling timer for {key}".format(key=key)
            )
            timer.cancel()
            timer = None

Please make sure the private key to access the Karaf console (see referenced post for how to obtain and configure) is located in the following file: ~openhab/.ssh/karaf_openhab_id.rsa
This translates to /var/lib/openhab/.ssh/karaf_openhab_id.rsa on openHABian.

Please also make sure the file permissions on the private keys is set to user read/write only. New versions of ssh will otherwise ignore these private keys when attempting at logging in, as with:

$ ssh -p 8101 -l openhab localhost -i ~/.ssh/karaf_openhab_id.rsa
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0660 for '/var/lib/openhab2/.ssh/karaf_openhab_id.rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/var/lib/openhab2/.ssh/karaf_openhab_id.rsa": bad permissions
Password authentication
Password: 

To set the permissions right on any Linux-based system, log in as the openhab user and do the following:

$ sudo -u openhab bash
$ cd ~/.ssh/
$ chmod go-rw karaf_openhab_id.rsa

NOTE: apply this to any other private key in the .ssh folder, e.g. to id_rsa if it exists.