Skip to content

openhab/openhab-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

315d2ed · Mar 26, 2025

History

32 Commits
Mar 26, 2025
Mar 25, 2025
Mar 15, 2025
Mar 26, 2025

Repository files navigation

Helper Library

This library is a Python library that supports access to automation in openHAB. It provides convenient access to common core openHAB functions that make the full range of Java APIs easily accessible and usable. It does not try to encapsulate every conceivable aspect of the OpenHAB API. Instead, it tries to simplify access to Java APIs and make it more intuitive, following typical python standards.

This library is included by default in the openHAB Python Scripting Add-on.

Creating Python Scripts

When this add-on is installed, you can select Python3 as a scripting language when creating a script action within the rule editor of the UI.

Alternatively, you can create scripts in the automation/python configuration directory. If you create an empty file called test.py, you will see a log line with information similar to:

... [INFO ] [ort.loader.AbstractScriptFileWatcher]] - (Re-)Loading script '/openhab/conf/automation/python/test.py'

To enable debug logging, use the console logging commands to enable debug logging for the automation functionality:

log:set DEBUG org.openhab.automation.pythonscripting

Scripting Basics

Lets start with a simple script

from openhab import rule
from openhab.triggers import GenericCronTrigger

@rule( triggers = [ GenericCronTrigger("*/5 * * * * ?") ] )
class Test:
    def execute(self, module, input):
        self.logger.info("Rule was triggered")

or another one, using the scope module

from openhab import rule
from openhab.triggers import ItemCommandTrigger

import scope

@rule( triggers = [ ItemCommandTrigger("Item1", scope.ON) ] )
class Test:
    def execute(self, module, input):
        self.logger.info("Rule was triggered")

::: tip Note By default, the scope, Registry and logger is automatically imported for UI based rules :::

PY3 Transformation

openHAB provides several data transformation services as well as the script transformations, that are available from the framework and need no additional installation. It allows transforming values using any of the available scripting languages, which means Python Scripting is supported as well. See the transformation docs for more general information on the usage of script transformations.

Use Python Scripting as script transformation by:

  1. Creating a script in the $OPENHAB_CONF/transform folder with the .py extension. The script should take one argument input and return a value that supports toString() or null:

    def calc(input):
        # Do some data transformation here, e.g.
        return "String has" + data.length + "characters";
    calc(input)
  2. Using PY3(<scriptname>.py):%s as Item state transformation.

  3. Passing parameters is also possible by using a URL like syntax: PY3(<scriptname>.py?arg=value). Parameters are injected into the script and can be referenced like variables.

Simple transformations can aso be given as an inline script: PY3(|...), e.g. PY3(|"String has " + input.length + "characters"). It should start with the | character, quotes within the script may need to be escaped with a backslash \ when used with another quoted string as in text configurations.

::: tip Note By default, the scope, Registry and logger is automatically imported for PY3 Transformation scripts :::

Examples

Simple rule

from openhab import rule, Registry
from openhab.triggers import GenericCronTrigger, ItemStateUpdateTrigger, ItemCommandTrigger, EphemerisCondition, when, onlyif

import scope

@rule()
@when("Time cron */5 * * * * ?")
def test1(module, input):
    test1.logger.info("Rule 1 was triggered")

@rule()
@when("Item Item1 received command")
@when("Item Item1 received update")
@onlyif("Today is a holiday")
def test2(module, input):
    Registry.getItem("Item2").sendCommand(scope.ON)

@rule( 
    triggers = [ GenericCronTrigger("*/5 * * * * ?") ]
)
class Test3:
    def execute(self, module, input):
        self.logger.info("Rule 3 was triggered")

@rule(
    triggers = [
        ItemStateUpdateTrigger("Item1"),
        ItemCommandTrigger("Item1", scope.ON)
    ],
    conditions = [
        EphemerisCondition("notholiday")
    ]
)
class Test4:
    def execute(self, module, input):
        if Registry.getItem("Item2").postUpdateIfDifferent(scope.OFF):
            self.logger.info("Item2 was updated")

Query thing status info

from openhab import logger, Registry

info = Registry.getThing("zwave:serial_zstick:512").getStatusInfo()
logger.info(info.toString());

Query historic item

from openhab import logger, Registry
from datetime import datetime

historicItem = Registry.getItem("Item1").getPersistence().persistedState( datetime.now().astimezone() )
logger.info( historicItem.getState().toString() );

historicItem = Registry.getItem("Item2").getPersistence("jdbc").persistedState( datetime.now().astimezone() )
logger.info( historicItem.getState().toString() );

Using scope

Simple usage of jsr223 scope objects

from openhab import Registry

from scope import ON

Registry.getItem("Item1").sendCommand(ON)

Logging

There are 3 ways of logging.

  1. using normal print statements. In this case they are redirected to the default openhab logfile and marked with log level INFO or ERROR
import sys

print("log message")

print("error message", file=sys.stderr)
  1. using the logging module. Here you get a logging object, already initialized with the prefix "org.openhab.automation.pythonscripting"
from openhab import logging

logging.info("info message")

logging.error("error message")
  1. using the rule based logging module. Here you get a logging object, already initialized with the prefix "org.openhab.automation.pythonscripting."
from openhab import rule
from openhab.triggers import GenericCronTrigger

@rule( triggers = [ GenericCronTrigger("*/5 * * * * ?") ] )
class Test:
    def execute(self, module, input):
        self.logger.info("Rule was triggered")

Decorators

decorator @rule

the decorator will register the decorated class as a rule. It will wrap and extend the class with the following functionalities

  • Register the class or function as a rule
  • If name is not provided, a fallback name in the form "{filename}.{function_or_classname}" is created
  • Triggers can be added with argument "triggers", with a function called "buildTriggers" (only classes) or with an @when decorator
  • Conditions can be added with argument "conditions", with a function called "buildConditions" (only classes) or with an @onlyif decorator
  • The execute function is wrapped within a try / except to provide meaningful error logs
  • A logger object (self.logger or {functionname}.logger) with the prefix "org.automation.pythonscripting.{filename}.{function_or_classname}" is available
  • You can enable a profiler to analyze runtime with argument "profile=1"
  • Every run is logging total runtime and trigger reasons
from openhab import rule
from openhab.triggers import GenericCronTrigger

@rule( triggers = [ GenericCronTrigger("*/5 * * * * ?") ] )
class Test:
    def execute(self, module, input):
        self.logger.info("Rule 3 was triggered")
2025-01-09 09:35:11.002 [INFO ] [tomation.pythonscripting.demo1.Test2] - Rule executed in    0.1 ms [Item: Item1]
2025-01-09 09:35:15.472 [INFO ] [tomation.pythonscripting.demo1.Test1] - Rule executed in    0.1 ms [Other: TimerEvent]

'execute' callback 'input' parameter

Depending on which trigger type is used, corresponding event objects are passed via the "input" parameter

The type of the event can also be queried via AbstractEvent.getTopic

decorator @when

@when("Item Test_String_1 changed from 'old test string' to 'new test string'")
@when("Item gTest_Contact_Sensors changed")
@when("Member of gTest_Contact_Sensors changed from ON to OFF")
@when("Descendent of gTest_Contact_Sensors changed from OPEN to CLOSED")

@when("Item Test_Switch_2 received update ON")
@when("Member of gTest_Switches received update")

@when("Item Test_Switch_1 received command")
@when("Item Test_Switch_2 received command OFF")

@when("Thing hue:device:default:lamp1 received update ONLINE")

@when("Thing hue:device:default:lamp1 changed from ONLINE to OFFLINE")

@when("Channel hue:device:default:lamp1:color triggered START")

@when("System started")
@when("System reached start level 50")

@when("Time cron 55 55 5 * * ?")
@when("Time is midnight")
@when("Time is noon")

@when("Time is 10:50")

@when("Datetime is Test_Datetime_1")
@when("Datetime is Test_Datetime_2 time only")

@when("Item added")
@when("Item removed")
@when("Item updated")

@when("Thing added")
@when("Thing removed")
@when("Thing updated")

decorator @onlyif

@onlyif("Item Test_Switch_2 equals ON")
@onlyif("Today is a holiday")
@onlyif("It's not a holiday")
@onlyif("Tomorrow is not a holiday")
@onlyif("Today plus 1 is weekend")
@onlyif("Today minus 1 is weekday")
@onlyif("Today plus 3 is a weekend")
@onlyif("Today offset -3 is a weekend")
@onylyf("Today minus 3 is not a holiday")
@onlyif("Yesterday was in dayset")
@onlyif("Time 9:00 to 14:00")

Modules

module scope

The scope module encapsulates all default jsr223 objects/presents into a new object. You can use it like below

from scope import * # this makes all jsr223 objects available

print(ON)
from scope import ON, OFF # this imports specific jsr223 objects

print(ON)
import scope # this imports just the module

print(scope.ON)

You can also import additional jsr223 presents like

from scope import RuleSimple
from scope import RuleSupport
from scope import RuleFactories
from scope import ScriptAction
from scope import cache
from scope import osgi

Additionally you can import all java classes from 'org.openhab' package like

from org.openhab.core import OpenHAB

print(str(OpenHAB.getVersion()))

module openhab

Class Usage Description
rule @rule( name=None, description=None, tags=None, triggers=None, conditions=None, profile=None) Rule decorator to wrap a custom class into a rule
logger logger.info, logger.warn ... Logger object with prefix 'org.automation.pythonscripting.{filename}'
Registry see Registry class Static Registry class used to get items, things or channels
Timer see Timer class Static Timer class to create, start and stop timers
Set see Set class Set object

module openhab.actions

Class Usage Description
Audio see openhab Audio api
BusEvent see openhab BusEvent api
Ephemeris see openhab Ephemeris api
Exec see openhab Exec api e.g. Exec.executeCommandLine(timedelta(seconds=1), "whoami")
HTTP see openhab HTTP api
Log see openhab Log api
Ping see openhab Ping api
ScriptExecution see openhab ScriptExecution api
Semantic see openhab Semantic api
ThingAction see openhab ThingAction api
Transformation see openhab Transformation api
Voice see openhab Voice api
NotificationAction e.g. NotificationAction.sendNotification("[email protected]", "Window is open")

module openhab.triggers

Class Usage Description
when @when(term_as_string) When trigger decorator to create a trigger by a term
onlyif @onlyif(term_as_string) Onlyif condition decorator to create a condition by a term
ChannelEventTrigger ChannelEventTrigger(channel_uid, event=None, trigger_name=None)
ItemStateUpdateTrigger ItemStateUpdateTrigger(item_name, state=None, trigger_name=None)
ItemStateChangeTrigger ItemStateChangeTrigger(item_name, state=None, previous_state=None, trigger_name=None)
ItemCommandTrigger ItemCommandTrigger(item_name, command=None, trigger_name=None)
GroupStateUpdateTrigger GroupStateUpdateTrigger(group_name, state=None, trigger_name=None)
GroupStateChangeTrigger GroupStateChangeTrigger(group_name, state=None, previous_state=None, trigger_name=None)
GroupCommandTrigger GroupCommandTrigger(group_name, command=None, trigger_name=None)
ThingStatusUpdateTrigger ThingStatusUpdateTrigger(thing_uid, status=None, trigger_name=None)
ThingStatusChangeTrigger ThingStatusChangeTrigger(thing_uid, status=None, previous_status=None, trigger_name=None)
SystemStartlevelTrigger SystemStartlevelTrigger(startlevel, trigger_name=None) for startlevel see openHAB StartLevelService API
GenericCronTrigger GenericCronTrigger(cron_expression, trigger_name=None)
TimeOfDayTrigger TimeOfDayTrigger(time, trigger_name=None)
DateTimeTrigger DateTimeTrigger(cron_expression, trigger_name=None)
PWMTrigger PWMTrigger(cron_expression, trigger_name=None)
GenericEventTrigger GenericEventTrigger(event_source, event_types, event_topic="/", trigger_name=None)
ItemEventTrigger ItemEventTrigger(event_types, item_name=None, trigger_name=None)
ThingEventTrigger ThingEventTrigger(event_types, thing_uid=None, trigger_name=None)
ItemStateCondition ItemStateCondition(item_name, operator, state, condition_name=None)
EphemerisCondition EphemerisCondition(dayset, offset=0, condition_name=None)
TimeOfDayCondition TimeOfDayCondition(start_time, end_time, condition_name=None)
IntervalCondition IntervalCondition(min_interval, condition_name=None)

Classes

class Registry

Function Usage Return Value
getThing getThing(uid) Thing
getChannel getChannel(uid) Channel
getItem getItem(item_name) Item or GroupItem
resolveItem resolveItem(item_or_item_name) Item or GroupItem
getItemState getItemState(item_name, default = None) openHAB State
getItemMetadata getItemMetadata(item_or_item_name, namespace) openHAB Metadata
setItemMetadata setItemMetadata(item_or_item_name, namespace, value, configuration=None) openHAB Metadata
removeItemMetadata removeItemMetadata(item_or_item_name, namespace = None) openHAB Metadata

class Item

Item is a wrapper around openHAB Item with additional functionality.

Function Usage Return Value
postUpdate postUpdate(state)
postUpdateIfDifferent postUpdateIfDifferent(state)
sendCommand sendCommand(command)
sendCommandIfDifferent sendCommandIfDifferent(command)
getPersistence getPersistence(service_id = None) ItemPersistence
getSemantic getSemantic() ItemSemantic
<...> see openHAB Item api

class GroupItem

GroupItem is an extended Item which wraps results from getAllMembers & getMembers into Items

class ItemPersistence

ItemPersistence is a wrapper around openHAB PersistenceExtensions. The parameters 'item' and 'serviceId', as part of the Wrapped Java API, are not needed, because they are inserted automatically.

Function Usage Description
getStableMinMaxState getStableMinMaxState(time_slot, end_time = None) Average calculation which takes into account the values ​​depending on their duration
getStableState getStableState(time_slot, end_time = None) Average calculation which takes into account the values ​​depending on their duration
<...> see openHAB PersistenceExtensions api

class ItemSemantic

ItemSemantic is a wrapper around openHAB Semantics. The parameters 'item', as part of the Wrapped Java API, is not needed because it is inserted automatically.

Function Usage
<...> see openHAB Semantics api

class Thing

Thing is a wrapper around openHAB Thing.

Function Usage
<...> see openHAB Thing api

class Channel

Channel is a wrapper around openHAB Channel.

Function Usage
<...> see openHAB Channel api

class Timer

Function Usage Description
createTimeout createTimeout(duration, callback, args=[], kwargs={}, old_timer = None, max_count = 0 ) Create a timer that will run callback with arguments args and keyword arguments kwargs, after duration seconds have passed. If old_timer from e.g previous call is provided, it will be stopped if not already triggered. If max_count together with old_timer is provided, then 'max_count' times the old timer will be stopped and recreated, before the callback will be triggered immediately

class Set

This is a helper class which makes it possible to use a python 'set' as an argument for java class method calls

Others

Threading

Thread or timer objects which was started by itself should be registered in the lifecycleTracker to be cleaned during script unload.

import scope
import threading

class Timer(theading.Timer):
    def __init__(self, duration, callback):
        super().__init__(duration, callback)

    def shutdown(self):
        if not self.is_alive():
            return
        self.cancel()
        self.join()

def test():
    print("timer triggered")

job = Timer(60, test)
job.start()

scope.lifecycleTracker.addDisposeHook(job.shutdown)

Timer objects created via openhab.Timer.createTimeout, however, automatically register in the disposeHook and are cleaned on script unload.

from openhab import Timer

def test():
    print("timer triggered")

Timer.createTimeout(60, test)

Below is a complex example of 2 sensor values ​​that are expected to be transmitted in a certain time window (e.g. one after the other).

After the first state change, the timer wait 5 seconds, before it updates the final target value. If the second value arrives before this time frame, the final target value is updated immediately.

from openhab import rule, Registry
from openhab.triggers import ItemStateChangeTrigger

@rule(
    triggers = [
        ItemStateChangeTrigger("Room_Temperature_Value"),
        ItemStateChangeTrigger("Room_Humidity_Value")
    ]
)
class UpdateInfo:
    def __init__(self):
        self.update_timer = None
    
    def updateInfoMessage(self):
        msg = "{}{} °C, {} %".format(Registry.getItemState("Room_Temperature_Value").format("%.1f"), Registry.getItemState("Room_Temperature_Value").format("%.0f"))
        Registry.getItem("Room_Info").postUpdate(msg)
        self.update_timer = None

    def execute(self, module, input):
        self.update_timer = Timer.createTimeout(5, self.updateInfoMessage, old_timer = self.update_timer, max_count=2 )

python <=> java conversion

Conversion occurs in both directions

Python class Java class
datetime with timezone ZonedDateTime
datetime without timezone Instant
timedelta Duration
list Collection
Set(set) Set
Item Item

limitations

  • graalby can't handle arguments in constructors of java objects. Means you can't instantiate a javaobject in python with a parameter. oracle/graalpython#367
  • graalpy does not really support python 'set' types as arguments of function calls to java objects oracle/graalpython#260
    • The reason is that Java is not able to distinguish what is a python list and what is a python set. A workaround is to use the class Set

About

No description, website, or topics provided.

Resources

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages