How to restart binding with in rules?

I don’t really have anything else to offer in teens of help. My experience with exec binding and executeCommandLine one guess so far.

Does it work if you put the commands into a script and executed the script?

Thanks anyway - your help is greatly appreciated.
I guess you are the one with (by far) the most responses to any kind of problems.

So again - thanks for your patience and endurance :slight_smile:

You have been right:
It does NOT work with:
executeCommandLine("/usr/bin/ssh -p 8101 -i /home/openhab/karaf_keys/openhab.id_rsa openhab@localhost 'bundle:restart org.openhab.binding.netatmo'", 6000)

but it DOES with the same stuff in a script:
executeCommandLine("/etc/openhab2/scripts/restartNetatmo.sh", 7000)

Now I remember, that I struggled a lot with the same inconsistant behavious, when I started with OH.
I am wondering, why this general issue is still present…

Because it is exceptionally difficult to execute command line scripts from Java and have it behave consistently across all operating systems and platforms.

1 Like

I have permission issues:
Execution failed (Exit value: -559038737. Caused by java.io.IOException: Cannot run program “/etc/openhab2/scripts/renewFritzBoxWANIP.sh” (in directory “.”): error=13, Permission denied)

any idea?

Works with:

executeCommandLine(“bash /etc/openhab2/scripts/renewFritzBoxWANIP.sh”, 7000)

Hi…

So I must embarrassingly admit that I am I the same situation almost. I an attempt to get a backup script to run without a password I messed with the sudoers file a have now removed my user openhabian from the sudoers…so now I cannot even run the backup command (even though I have a completely fresh backup).
I am work on a Mac, and have inserted the SD card like you say above, but how do I find the specific path to the sd card sudoers file in terminal? …thanks in advance form a desperate man.,…:slight_smile:

I don’t use Mac so I’ve no idea how to mount the SD card or where it appears when mounted.

@rlkoshak Rich, not quite clear on this.

I did

ssh-keygen -t dsa -f karaf.id_dsa -N karaf

but I am not clear on this step…

You can copy in the content of the karaf.id_dsa.pub file in the etc/keys.properties:

Does that mean I should open keys.properties and cut and paste the contents of the kara.id_dsa.pub file into it?

Yes. It has been ages since I’ve done this but I think the comments in the file that tell you what you need to do.

I think you need to add a line that has openHAB=<paste your public key here>,<copy the roles from the karaf line>

I don’t currently have this set up so I’m going from memory here.

I still don’t fully get what needs to be done, as there’s already an entry for the openhab user in /var/lib/openhab2/etc/users.properties (it’s surrounded by {CRYPT} tags):

openhab = {CRYPT}xxxxxxxx{CRYPT},_g_:admingroup

Do I have to replace the current line for the openhab user with a line containing the public key (and the group) from karaf.id_rsa.pub generated according to the Karaf instructions? As in:

#openhab = {CRYPT}xxxxxxxx{CRYPT},_g_:admingroup
openhab = (CONTENT_OF_ karaf.id_rsa.pub_GOES_HERE),_g_:admingroup

where (CONTENT_OF_ karaf.id_rsa.pub_GOES_HERE) is replaced with the content of karaf.id_rsa.pub.

Is there something else needed, e.g. setting an entry in ~/.ssh/authorized_keys for the openhabian user?

It’s been years since I’ve done this. But I believe you need to add an entry to keys.properties, not users.properties. At least that is where I added my key.

authorized_keys controls what certificates are allowed to log into your host operating system’s account. By adding your key to keys.properties, you are essentially editing the equivalent of authorized_keys for the Karaf Console.

When copying your key over from the .pub file, make sure to only copy the key part (the random string). Omit the ssh-rsa at the beginning and anything after it.

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.

I simplified the script by defining keyThingsDict globally and by extending the structure of the dict. As a result I could use a decorator generator method (key_things_trigger_generator_thing_changed()) to take care of adding the @when() triggers, instead of me having to manage a duplicate set:

keyThingsDict = {
    # Z-Wave controller (ZME E UZB1)
    "zwave:serial_zstick:controller": {
        "thing_name": "Z-Wave Controller (ZME-E-UZB1)",
        "status_item": "ZWave_Controller_Status",
    },
    # Fibaro Roller Shutter 3 (first floor betdrooms)
    "zwave:device:controller:node2": {
        "thing_name": "Bedroom NE Roller Shutter (Fibaro FGR-223)",
        "status_item": "Bedroom_NE_Shutter_Status",
    },
    "zwave:device:controller:node3": {
        "thing_name": "Bedroom SE Roller Shutter (Fibaro FGR-223)",
        "status_item": "Bedroom_SE_Shutter_Status",
    },
    "zwave:device:controller:node4": {
        "thing_name": "Bedroom SW Roller Shutter (Fibaro FGR-223)",
        "status_item": "Bedroom_SW_Shutter_Status",
    },
    # Fakro ARZ Z-Wave (attic roof shutters)
    "zwave:device:controller:node5": {
        "thing_name": "Attic NE Roof Shutter (Fakro ARZ-ZWave)",
        "status_item": "Attic_Shutter_NE_Status",
    },
    "zwave:device:controller:node6": {
        "thing_name": "Attic SE Roof Shutter (Fakro ARZ-ZWave)",
        "status_item": "Attic_Shutter_SE_Status",
    },
    "zwave:device:controller:node7": {
        "thing_name": "Attic SW Roof Shutter (Fakro ARZ-ZWave)",
        "status_item": "Attic_Shutter_SW_Status",
    },
    # Fakro ZWP-10 portable controllers
    "zwave:device:controller:node8": {
        "thing_name": "Fakro ZWP10 Portable Remote #1",
        "status_item": "ZWave_Remote_ZWP10_1_Status",
    },
    "zwave:device:controller:node9": {
        "thing_name": "Fakro ZWP10 Portable Remote #2",
        "status_item": "ZWave_Remote_ZWP10_2_Status",
    },
    "zwave:device:controller:node10": {
        "thing_name": "Fakro ZWP10 Portable Remote #3",
        "status_item": "ZWave_Remote_ZWP10_3_Status",
    },
    # IKEA TRÅDFRI:
    "tradfri:gateway:gwa0b1c2d3e4f5": {
        "thing_name": "IKEA TRADFRI Gateway",
        "status_item": "IKEA_TRADFRI_Gateway_Status",
        "restart_info": {
            "binding_uri": "org.openhab.binding.tradfri",
            "wait_time": 60,
            "reschedule_timer_on_update": True,
            "notify_restart": True,
        },
    },
    # OWM Binding:
    "openweathermap:weather-api:a1b2c3d4": {
        "thing_name": "OpenWeatherMaps: API",
        "status_item": "Wx_OWM_API_Status",
    },
    "openweathermap:weather-and-forecast:a1b2c3d4:local": {
        "thing_name": "OpenWeatherMaps: Current Weather & Forecast",
        "status_item": "Wx_OWM_Weather_Status",
        "restart_info": {
            "binding_uri": "org.openhab.binding.openweathermap",
            "wait_time": 60,
            "reschedule_timer_on_update": True,
            "notify_restart": True,
        },
    },
    # BuienRadar binding:
    "buienradar:rain_forecast:home": {
        "thing_name": "Buienradar: Rain Forecast",
        "status_item": "BuienRadar_Status",
        "restart_info": {
            "binding_uri": "org.openhab.binding.buienradar",
            "wait_time": 90,
            #"reschedule_timer_on_update": False,
            #"notify_restart": False,
        },
    },
}


# Automatically add the needed decorators for all monitored Things in things_dict (keyThingsDict)
def key_things_trigger_generator_thing_changed(things_dict):
    def generated_triggers(function):
        global logTitle
        for k in keyThingsDict.keys():
            logPrefix = u"generated_triggers(): adding @when(\"Thing '{}' changed\") trigger for '{}'".format(
                k, unicode(keyThingsDict.get(k).get("thing_name"))
            )
            LogAction.logInfo(logTitle, logPrefix)
            when("Thing '{}' changed".format(k))(function)
        return function

    return generated_triggers


@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 for the rule to work."
    + ruleTimeStamp,
    tags=["watchdog", ruleTimeStamp],
)
@when("Time cron 0 0/10 * * * ?")
@key_things_trigger_generator_thing_changed(keyThingsDict)
def Rule_KeyThingStatusUpdate(event):
    global logTitle
    global keyThingsDict
    logPrefix = "Rule_KeyThingStatusUpdate(): "

    LogAction.logInfo(logTitle, logPrefix + "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:
        keyItemInfo = keyThingsDict[k]
        keyStatusItem = keyItemInfo.get("status_item")
        keyStatusItemThingName = keyItemInfo.get("thing_name")
        bindingRestartInfo = keyItemInfo.get("restart_info")
        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
        LogAction.logInfo(
            logTitle,
            logPrefix
            + "Thing '{node_name}' ({name} with status item {item_name}) status changed to '{node_state}'".format(
                node_name=nodeName,
                name=keyStatusItemThingName,
                item_name=keyStatusItem,
                node_state=nodeState,
            ),
        )
        events.postUpdate(keyStatusItem, str(nodeState))

        # Restart some bindings if needed
        if bindingRestartInfo:
            LogAction.logInfo(
                logTitle,
                logPrefix
                + u"Will attempt at restarting the {} binding with URI {} - current status is {}".format(
                    keyStatusItemThingName,
                    bindingRestartInfo.get("binding_uri"),
                    nodeState,
                ),
            )
            # Note: schedule_binding_restart() takes care of managing the Thing status, so don't do it here:
            schedule_binding_restart(
                bindingRestartInfo.get("binding_uri"),
                keyStatusItemThingName,
                k,
                bindingRestartInfo.get("wait_time"),
                reschedule_timer_on_update=bindingRestartInfo.get(
                    "reschedule_timer_on_update", False
                ),
                notify_restart=bindingRestartInfo.get("notify_restart", False),
            )

The remainder of the script is unchanged.

1 Like

@shutterfreak , can you post the total code op wathdog.py, I have difficulty in combining your last two posts, since I am an early beginner in python.

Thanks Herman

Here you go (probably only works on openHAB2 unless JSR223 support has now also been ported to openHAB3):

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 | "

telegramBotName = "YouTelegramBotNameGoesHere"


def on_load():
    Rule_KeyThingStatusUpdate(None)
    Rule_UpdateLastSeen(None)
    Rule_CheckOWMLastResponse(None)


def getlastSeenItemFromBatteryLevelItem(item_name):
    if item_name is None:
        return None
    if not item_name[-12:] == "BatteryLevel":
        return None
    return item_name[:-12] + "LastSeen"


@rule(
    rulePrefix + "Update last seen gBatteryLevel item",
    description="This rule updates the 'last seen' item in the 'gBatteryLevel' group. This allows to keep track of dead battery-operated items, e.g. due to a depleted battery."
    + ruleTimeStamp,
    tags=["watchdog", ruleTimeStamp],
)
# @when("System started")
@when("Member of gBatteryLevel changed")
@when("Member of gBatteryLevel received update")
def Rule_UpdateLastSeen(event):
    global logTitle
    LogAction.logInfo(
        logTitle,
        "At the start of Rule_UpdateLastSeen() - item is {itemName}.".format(
            itemName=event.itemName if event else "none"
        ),
    )

    ts_now_str = DateTime.now().toString()
    items = list()
    if event:
        lastSeenItemName = getlastSeenItemFromBatteryLevelItem(str(event.itemName))
        lastSeenItem = ir.getItem(lastSeenItemName)
        LogAction.logInfo(
            logTitle,
            "{itemName} changed to {itemState} -- Last Seen item is '{lastSeenItemName}' ({itemExists})".format(
                itemName=event.itemName,
                itemState=event.itemState.toString(),
                lastSeenItemName=lastSeenItemName,
                itemExists="Ok"
                if lastSeenItem
                else "Error: not found in item registry",
            ),
        )
        """
        Telegram.sendTelegram(
            telegramBotName,
            u"<b>{itemName}</b> state changed to <code>{itemState}</code> at {timestamp}".format(
                itemName=event.itemName,
                itemState=event.itemState.toString(),
                timestamp=ts_now_str,
            ),
        )
        """
        if lastSeenItem is None:
            LogAction.logError(
                logTitle,
                "LastSeen item '{lastSeenItemName}' related to BatteryLevel item '{batteryLevelItemName}' not found in the item registry".format(
                    lastSeenItemName=lastSeenItemName,
                    batteryLevelItemName=event.itemName,
                ),
            )
            return
        LogAction.logInfo(
            logTitle,
            "Will now append item '{lastSeenItemName}' to the items list.".format(
                lastSeenItemName=lastSeenItemName
            ),
        )
        items.append(lastSeenItem)
    else:
        LogAction.logInfo(
            logTitle,
            "Will now add all items belonging to the 'gBatteryLastSeen' group item to the items list.",
        )
        grp = ir.getItem("gBatteryLastSeen")
        if grp is None:
            LogAction.logError(
                logTitle,
                "gBatteryLastSeen not found in the item registry. Nothing to do",
            )
            return
        # The triggered item has a name ending with "BatteryLevel", the item to update ends in "LastSeen"
        items = list(item for item in grp.getMembers() if item.type != "Group")

    for lastSeenItem in items:
        LogAction.logInfo(
            logTitle,
            "Processing {itemName} at timestamp {timestamp}.".format(
                itemName=lastSeenItem.name, timestamp=ts_now_str
            ),
        )
        # Only update Last Seen if item changed or received update (not on system restart as the value is restored from persistence)
        if event:
            LogAction.logInfo(
                logTitle,
                "Setting the last seen timestamp for {itemName} to {timestamp}.".format(
                    itemName=lastSeenItem.name, timestamp=ts_now_str
                ),
            )
            events.postUpdate(lastSeenItem, ts_now_str)

        # Status item is updated irrespectively of how we were triggered (hopefully not through PaperUI though)
        statusItem = ir.getItem(lastSeenItem.name + "Status")
        if statusItem is None:
            LogAction.logError(
                logTitle,
                "Item '{itemName}' not found in the item registry. Nothing to do.".format(
                    itemName=lastSeenItem.name + "Status"
                ),
            )
            continue
        LogAction.logInfo(
            logTitle,
            "Setting '{itemName}' to '{value}'".format(
                itemName=statusItem.name,
                value="ONLINE" if event else "SYSTEM RESTARTED",
            ),
        )
        # Trigger the expire binding with sendCommand():
        events.sendCommand(statusItem, "ONLINE" if event else "SYSTEM RESTARTED")


# Binding restart timers:
timers = {}
# Binding restarts:
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(
                telegramBotName,
                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(
                        telegramBotName,
                        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:
                # 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

                LogAction.logInfo(
                    logTitle,
                    u"Will now restart the {binding_name} binding (Thing UID = '{thing_uid}', current status is '{state}', restart #{cnt}).".format(
                        cnt=binding_restarts[binding_id],
                        binding_name=binding_name,
                        thing_uid=binding_thing_name,
                        state=current_state,
                    ),
                )

                if notify_restart is True:
                    Telegram.sendTelegram(
                        telegramBotName,
                        u"<b>Automatic binding restart</b> #{cnt} of the {binding_name} binding (Thing UID = '{thing_uid}', current status is '{state}')".format(
                            cnt=binding_restarts[binding_id],
                            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 + "Check OWM last response",
    description="This rule checks whether the OpenWeatherMap weather reporting and forecast item is still alive. If alive, then 'Wx_OWM_Forecast_Reporting_Status' will be set to 'ONLINE', else an error state will be provided."
    + ruleTimeStamp,
    tags=["watchdog", "OWM", "Weather", ruleTimeStamp],
)
# @when("System started")
@when("Item Wx_OWM_Current_Time changed")
@when("Time cron 30 0/5 * * * ?")
def Rule_CheckOWMLastResponse(event):
    global logTitle

    forecast_timestamp_item = itemRegistry.getItem("Wx_OWM_Current_Time")
    if forecast_timestamp_item is None:
        # forecast_timestamp_item.state is not set
        LogAction.logError(
            logTitle,
            "Wx_OWM_Current_Time not found in the item registry. Nothing to do",
        )
        events.postUpdate(
            "Wx_OWM_Forecast_Reporting_Status",
            "ERROR - Wx_OWM_Current_Time not found in item registry",
        )
        return

    if not isinstance(forecast_timestamp_item.state, DateTimeType):
        # forecast_timestamp_item.state is not set
        LogAction.logWarn(
            logTitle,
            "Wx_OWM_Current_Time.state is not a DateTimeType value: '{state}' -- OWM binding crashed or service probably down".format(
                state=forecast_timestamp_item.state
            ),
        )
        events.postUpdate(
            "Wx_OWM_Forecast_Reporting_Status",
            "ERROR - invalid state: '{state}'".format(
                state=str(forecast_timestamp_item.state)
            ),
        )
        return

    # DateTime.now().plusMinutes(earliest_rain_time)
    forecast_timestamp = DateTime(forecast_timestamp_item.state.toString())
    if forecast_timestamp.plusMinutes(10).isBefore(DateTime.now()):
        events.postUpdate(
            "Wx_OWM_Forecast_Reporting_Status",
            "ERROR - no update in the past 10 minutes",
        )
    else:
        events.postUpdate("Wx_OWM_Forecast_Reporting_Status", "ONLINE")


keyThingsDict = {
    # Z-Wave controller (ZME E UZB1)
    "zwave:serial_zstick:controller": {
        "thing_name": "Z-Wave Controller (ZME-E-UZB1)",
        "status_item": "ZWave_Controller_Status",
    },
    # Fibaro Roller Shutter 3 (first floor betdrooms)
    "zwave:device:controller:node2": {
        "thing_name": "Bedroom NE Shutter (Fibaro FGR-223 ZW5 v5.1 Roller Shutter 3)",
        "status_item": "Bedroom_NE_Shutter_Status",
    },
    "zwave:device:controller:node3": {
        "thing_name": "Bedroom SE Shutter (Fibaro FGR-223 ZW5 v5.1 Roller Shutter 3)",
        "status_item": "Bedroom_SE_Shutter_Status",
    },
    "zwave:device:controller:node4": {
        "thing_name": "Bedroom SW Shutter (Fibaro FGR-223 ZW5 v5.1 Roller Shutter 3)",
        "status_item": "Bedroom_SW_Shutter_Status",
    },
    # Fakro ARZ Z-Wave (attic roof shutters)
    "zwave:device:controller:node5": {
        "thing_name": "Attic NE Roof Shutter (Fakro ARZ-ZWave)",
        "status_item": "Attic_Shutter_NE_Status",
    },
    "zwave:device:controller:node6": {
        "thing_name": "Attic SE Roof Shutter (Fakro ARZ-ZWave)",
        "status_item": "Attic_Shutter_SE_Status",
    },
    "zwave:device:controller:node7": {
        "thing_name": "Attic SW Roof Shutter (Fakro ARZ-ZWave)",
        "status_item": "Attic_Shutter_SW_Status",
    },
    # Fakro ZWP-10 portable controllers
    "zwave:device:controller:node8": {
        "thing_name": "Fakro ZWP10 Portable Remote #1)",
        "status_item": "ZWave_Remote_ZWP10_1_Status",
    },
    "zwave:device:controller:node9": {
        "thing_name": "Fakro ZWP10 Portable Remote #2)",
        "status_item": "ZWave_Remote_ZWP10_2_Status",
    },
    "zwave:device:controller:node10": {
        "thing_name": "Fakro ZWP10 Portable Remote #3)",
        "status_item": "ZWave_Remote_ZWP10_3_Status",
    },
    # Fibaro FGSD-002 ZW5 v3.3 smoke/fire sensors
    "zwave:device:controller:node11": {
        "thing_name": "Fibaro FGSD-002 ZW5 v3.3 Smoke/Fire Alarm #1 - Living Room)",
        "status_item": "ZWave_SmokeAlarm_1_Status",
    },
    "zwave:device:controller:node12": {
        "thing_name": "Fibaro FGSD-002 ZW5 v3.3 Smoke/Fire Alarm #2 - 1st Floor)",
        "status_item": "ZWave_SmokeAlarm_2_Status",
    },
    "zwave:device:controller:node13": {
        "thing_name": "Fibaro FGSD-002 ZW5 v3.3 Smoke/Fire Alarm #3 - Attic)",
        "status_item": "ZWave_SmokeAlarm_3_Status",
    },
    # IKEA HOME SMART (TRÅDFRI):
    "tradfri:gateway:gwa0b1c2d3e4f5": {
        "thing_name": "IKEA TRADFRI Gateway",
        "status_item": "IKEA_TRADFRI_Gateway_Status",
        "restart_info": {
            "binding_uri": "org.openhab.binding.tradfri",
            "wait_time": 60,
            "reschedule_timer_on_update": True,
            "notify_restart": True,
        },
    },
    # OWM Binding:
    "openweathermap:weather-api:4afe5c0d": {
        "thing_name": "OpenWeatherMaps: API",
        "status_item": "Wx_OWM_API_Status",
    },
    "openweathermap:weather-and-forecast:a1b2c3d4:local": {
        "thing_name": "OpenWeatherMaps: Current Weather & Forecast",
        "status_item": "Wx_OWM_Weather_Status",
        "restart_info": {
            "binding_uri": "org.openhab.binding.openweathermap",
            "wait_time": 60,
            "reschedule_timer_on_update": True,
            "notify_restart": True,
        },
    },
    # BuienRadar binding:
    "buienradar:rain_forecast:home": {
        "thing_name": "Buienradar: Rain Forecast",
        "status_item": "BuienRadar_Status",
        "restart_info": {
            "binding_uri": "org.openhab.binding.buienradar",
            "wait_time": 90,
            # "reschedule_timer_on_update": False,
            # "notify_restart": False,
        },
    },
}

# Automatically add the needed decorators for all monitored Things in things_dict (keyThingsDict)
def key_things_trigger_generator_thing_changed(things_dict):
    def generated_triggers(function):
        global logTitle
        for k in keyThingsDict.keys():
            logPrefix = u"generated_triggers(): adding @when(\"Thing '{}' changed\") trigger for '{}'".format(
                k, unicode(keyThingsDict.get(k).get("thing_name"))
            )
            LogAction.logInfo(logTitle, logPrefix)
            when("Thing '{}' changed".format(k))(function)
        return function

    return generated_triggers


@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 for the rule to work."
    + ruleTimeStamp,
    tags=["watchdog", ruleTimeStamp],
)
# @when("System started")
@when("Time cron 0 0/10 * * * ?")
@key_things_trigger_generator_thing_changed(keyThingsDict)
def Rule_KeyThingStatusUpdate(event):
    global logTitle
    global keyThingsDict
    logPrefix = "Rule_KeyThingStatusUpdate(): "

    LogAction.logInfo(logTitle, logPrefix + "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:
        keyItemInfo = keyThingsDict[k]
        keyStatusItem = keyItemInfo.get("status_item")
        keyStatusItemThingName = keyItemInfo.get("thing_name")
        bindingRestartInfo = keyItemInfo.get("restart_info")
        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
        LogAction.logInfo(
            logTitle,
            logPrefix
            + "Thing '{node_name}' ({name} with status item {item_name}) status changed to '{node_state}'".format(
                node_name=nodeName,
                name=keyStatusItemThingName,
                item_name=keyStatusItem,
                node_state=nodeState,
            ),
        )
        events.postUpdate(keyStatusItem, str(nodeState))

        # Restart some bindings if needed
        if bindingRestartInfo:
            LogAction.logInfo(
                logTitle,
                logPrefix
                + u"Will attempt at restarting the {} binding with URI {} - current status is {}".format(
                    keyStatusItemThingName,
                    bindingRestartInfo.get("binding_uri"),
                    nodeState,
                ),
            )
            # Note: schedule_binding_restart() takes care of managing the Thing status, so don't do it here:
            schedule_binding_restart(
                bindingRestartInfo.get("binding_uri"),
                keyStatusItemThingName,
                k,
                bindingRestartInfo.get("wait_time"),
                reschedule_timer_on_update=bindingRestartInfo.get(
                    "reschedule_timer_on_update", False
                ),
                notify_restart=bindingRestartInfo.get("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

You will have to take care of defining the proper things and items (see keyThingsDict, but also gBatteryLevel, gBatteryLastSeen, Wx_OWM_Current_Time, Wx_OWM_Forecast_Reporting_Status … used in the script)

If you don’t plan to use a Telegram bot for reporting, then you can safely delete or comment out all Telegram related actions in the script (the from core.actions import Telegram import and the subsequent Telegram.sendTelegram() code blocks)

1 Like

JSR223 is now the default and only rules engine in OH 3. Rules DSL was ported to use the JSR223 rules engine. However, in OH 3 all references to org.eclipse.smarthome are gone, replaced with org.openhab. Other breaking changes include Joda is gone and java.time.ZonedDateTime is it’s replacement. executeCommandLine has also changed it’s arguments.

Note, the OH 3 compatible version of the Helper Libraries can be found at GitHub - CrazyIvan359/openhab-helper-libraries: JSR223-Jython scripts and modules for use with openHAB

1 Like

Thanks for script

How to restart bindings:

  • Install sshpass sudo apt install sshpass
  • Login as openhab user sudo -u openhab bash
  • Start console ssh openhab@localhost -p8101, type yes to add to known hosts, you see something like this:
The authenticity of host '[localhost]:8101 ([127.0.0.1]:8101)' can't be established.
RSA key fingerprint is SHA256:7xxxxxxjpuGkYkeUYazZxxxxxxxUH0xxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:8101' (RSA) to the list of known hosts.
  • Try to execute sshpass -p 'habopen' ssh openhab@localhost -p8101 'bundle:list'
    You should see bundles list.
  • Now you can restart binding from rules:
val out = executeCommandLine(Duration.ofSeconds(2), ("sshpass -p habopen ssh openhab@localhost -p8101 bundle:restart org.openhab.binding.yamahamusiccast").split(" "))

Change yamahamusiccast to your binding.

1 Like

If you set up ssh certs you can skip the sshpass stuff and not have your password exposed as plain text in the rules. There are tons of tutorials on how to generate ssh key pairs. Then see Security for details on how to apply that. Then it won’t even ask for a password.

2 Likes

I have confirmed this syntax works with OH 3.x with the following rule below.

rule "Restart WeMo Binding"
	when
		Item Restart_WeMo_Switch changed to ON
	then

		if (systemStarted.state != ON && OH_Uptime_HumanReadable.state != NULL && OH_Uptime_HumanReadable.state !== null && OH_Uptime_HumanReadable.state.toString() != 'UNDEF') {

			try {
					var String results = executeCommandLine(Duration.ofSeconds(5), ("sshpass -p habopen ssh openhab@localhost -p8101 bundle:restart org.openhab.binding.wemo").split(" "))
					if (results === null || results == NULL || results.toString() == '') {

						logInfo("WEMO","** Restarting WeMo Binding Succeeded.")
					}	

				} catch (Exception d) { 
				
					logError("WEMO","** Restarting WeMo Binding Failed.  Exception is " + d.getMessage)
				}
		}
end

Best, Jay

1 Like