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.
When this add-on is installed, you can select Python 3 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
Lets start with a simple rule
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")
NOTE By default, the scope, Registry and logger is automatically imported for UI based rules
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:
-
Creating a script in the
$OPENHAB_CONF/transform
folder with the.py
extension. The script should take one argumentinput
and return a value that supportstoString()
ornull
:"String has " + str(len(input)) + " characters"
or
def calc(input): if input is None: return 0 return "String has " + str(len(input)) + " characters" calc(input)
-
Using
PY(<scriptname>.py):%s
as Item state transformation. -
Passing parameters is also possible by using a URL like syntax:
PY(<scriptname>.py?arg=value)
. Parameters are injected into the script and can be referenced like variables.
Simple transformations can also be given as an inline script: PY(|...)
, e.g. PY(|"String has " + str(len(input)) + "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.
NOTE
By default, the scope, Registry and logger is automatically imported for PY
Transformation scripts
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")
from openhab import logger, Registry
info = Registry.getThing("zwave:serial_zstick:512").getStatusInfo()
logger.info(info.toString());
from openhab import logger, Registry
from datetime import datetime
historicItem = Registry.getItem("Item1").getPersistence().persistedState( datetime.now() )
logger.info( historicItem.getState().toString() );
historicItem = Registry.getItem("Item2").getPersistence("jdbc").persistedState( datetime.now() )
logger.info( historicItem.getState().toString() );
Simple usage of jsr223 scope objects
from openhab import Registry
from scope import ON
Registry.getItem("Item1").sendCommand(ON)
There are 3 ways of logging.
- Normal print statements, are redirected to the default openHAB logfile, prefixed with "org.openhab.automation.pythonscripting" and marked with log level INFO or ERROR
import sys
print("log message")
print("error message", file=sys.stderr)
- Using the logging module behaves like normal print statements, are prefixed with "org.openhab.automation.pythonscripting" and marked with log level INFO, ERROR, WARN ...
from openhab import logging
logging.info("info message")
logging.error("error message")
logging.warn("warning message")
- In Addition, the rule based logging module, is prefixed with "org.openhab.automation.pythonscripting.<RuleClassName>"
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")
The following checks for NULL
and UNDEF
. Both are enum values of scope.UnDefType
.
from openhab import Registry
import scope
state = Registry.getItemState("Item1")
if !isinstance(state, scope.UnDefType):
print("STATE: " + str(state))
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=
, a functionbuildTriggers
or with an @when decorator - Conditions can be added with argument
conditions=
, a functionbuildConditions
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)
, prefixed withorg.automation.pythonscripting.{filename}.{function_or_classname}
, is available - You can enable a profiler to analyze runtime with argument
profile_code=True
- Every run is logging total runtime and trigger reasons. This can be disabled with argument
runtime_measurement=False
from openhab import rule
from openhab.triggers import GenericCronTrigger
@rule( triggers = [ GenericCronTrigger("*/5 * * * * ?") ] )
class Test1:
def execute(self, module, input):
self.logger.info("Test1 was triggered")
@rule()
class Test2:
def buildTriggers(self):
return [ GenericCronTrigger("*/5 * * * * ?") ]
def execute(self, module, input):
self.logger.info("Test2 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
from openhab.triggers import 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")
from openhab.triggers import 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")
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 'java.' or 'org.openhab.' package like
from java.lang import Object
from org.openhab.core import OpenHAB
print(str(OpenHAB.getVersion()))
The module openhab
is a forward to the sub_module openhab.helper
and makes public useable modules available. e.g.
from openhab import Registry
can also be reewritten as
from openhab.helper import Registry
but you should always use the short variant.
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 |
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 | |
Things | see openHAB Things API | |
Transformation | see openHAB Transformation API | |
Voice | see openHAB Voice API | |
NotificationAction | e.g. NotificationAction.sendNotification("[email protected]", "Window is open") |
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) |
from openhab import Registry
Function | Usage | Return Value |
---|---|---|
getThings | Registry.getThings() | Array of openHAB Things |
getThing | Registry.getThing(uid) | openHAB Thing |
getChannel | Registry.getChannel(uid) | openHAB Channel |
getItemState | Registry.getItemState(item_name, default = None) | openHAB State |
getItem | Registry.getItem(item_name) | Item |
resolveItem | Registry.resolveItem(item_or_item_name) | Item |
addItem | Registry.addItem(item_config) | Item |
Item is a subclass of openHAB Item with additional functionality.
There is no need to import this class directly. It is returned as a result of function calls of the Registry class.
Function | Usage | Return Value |
---|---|---|
postUpdate | <instance>.postUpdate(state) | |
postUpdateIfDifferent | <instance>.postUpdateIfDifferent(state) | |
sendCommand | <instance>.sendCommand(command) | |
sendCommandIfDifferent | <instance>.sendCommandIfDifferent(command) | |
getPersistence | <instance>.getPersistence(service_id = None) | ItemPersistence |
getSemantic | <instance>.getSemantic() | ItemSemantic |
getMetadata | <instance>.getMetadata() | ItemMetadata |
buildSafeName | Item.buildSafeName(item_name) | Escaped string |
<...> | see openHAB Item API |
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.
There is no need to import this class directly. It is returned as a result of the function call Item.getPersistence().
Function | Usage | Description |
---|---|---|
getStableMinMaxState | <instance>.getStableMinMaxState(time_slot, end_time = None) | Average calculation which takes into account the values depending on their duration |
getStableState | <instance>.getStableState(time_slot, end_time = None) | Average calculation which takes into account the values depending on their duration |
<...> | see openHAB PersistenceExtensions API |
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.
There is no need to import this class directly. It is returned as a result of the function call Item.getSemantic().
Function | Usage |
---|---|
<...> | see openHAB Semantics API |
ItemMetadata is a proxy class to manage openHAB Metadata
There is no need to import this class directly. It is returned as a result of the function call Item.getMetadata().
Function | Usage | Description |
---|---|---|
get | <instance>.get(namespace) | openHAB Metadata |
set | <instance>.set(namespace, value, configuration=None) | openHAB Metadata |
remove | <instance>.remove(namespace) | openHAB Metadata |
removeAll | <instance>.removeAll() |
from openhab import Timer
Function | Usage | Description |
---|---|---|
createTimeout | Timer.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 |
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 )
In addition to standard value type mappings, the following type mappings are available.
Python class | Java class |
---|---|
datetime | ZonedDateTime |
datetime | Instant |
timedelta | Duration |
list | List |
list | Set |
Item | Item |