Idea: use python to directly generate rules files

I am the maintainer and since I literally answered your post I think I’m part of the OH community, too. :wink:

This!

All,

Point(s) taken. I will read more before complaining :flushed:

But, please consider my complaints also a valuable feedback on the OH learning curve. As a skilled engineer I find myself lost every now and then within the OH way of working. Maybe it is just as it is, maybe you see opportunities. :slight_smile:

Cheers,
Matthijs

While relaxing tonight, I whipped up an example of using scripted automation with Jython and the helper libraries to scrape the webpage to update DateTimeItems and send notifications through Telegram (the OH 1.x action version). The easiest way to install Jython is to use the beta bundle. Hopefully this is useful for you!

"""
This script contains a rule that will scrape https://afvalkalender.waalre.nl
for collection times twice a day and update Items with the dates. The script
will create the Items needed, if they do not exist.
"""
import re

from core.rules import rule
from core.triggers import when
from core.items import add_item
from core.actions import HTTP, Telegram

URI = "https://afvalkalender.waalre.nl/adres/5581BG:17"

def monthToNum(shortMonth):
    return{
        'jan' : 1,
        'feb' : 2,
        'mrt' : 3,
        'apr' : 4,
        'mei' : 5,
        'jun' : 6,
        'jul' : 7,
        'aug' : 8,
        'sep' : 9,
        'okt' : 10,
        'nov' : 11,
        'dec' : 12
    }[shortMonth]

def convert_date(day_text):
    day_list = day_text.split(" ")
    new_date = DateTimeType().zonedDateTime.withMonth(monthToNum(day_list[2])).withDayOfMonth(int(day_list[1])).withHour(0).withMinute(0).withSecond(0).withNano(0)
    return new_date

if ir.getItems("Afval_gft") == []:
    add_item("Afval_gft", item_type="DateTime", label="Groente [%s]", category="Calendar", groups=[], tags=[])
if ir.getItems("Afval_pmd") == []:
    add_item("Afval_pmd", item_type="DateTime", label="Plastic [%s]", category="Calendar", groups=[], tags=[])
if ir.getItems("Afval_papier") == []:
    add_item("Afval_papier", item_type="DateTime", label="Papier [%s]", category="Calendar", groups=[], tags=[])
if ir.getItems("Afval_kga") == []:
    add_item("Afval_kga", item_type="DateTime", label="Chemisch [%s]", category="Calendar", groups=[], tags=[])
if ir.getItems("Afval_rest") == []:
    add_item("Afval_rest", item_type="DateTime", label="Restafval [%s]", category="Calendar", groups=[], tags=[])

@rule("Send trash pickup notifications")
@when("Time cron 0 30 7 * * ?")
@when("Time cron 0 30 19 * * ?")
def send_trash_pickup_notifications(event):
    # get the page
    the_page = HTTP.sendHttpGetRequest(URI, 5000)
    if the_page:
        contents = the_page.decode('utf-8')

        # Find the part of the page with id=ophaaldata
        START = '<ul id="ophaaldata" class="line">'
        END = '</ul>'
        section = re.search("{}(.*?){}".format(START, END), contents, re.DOTALL).group(1)

        # Split the section based on "\n" into lines.
        section_list = section.split("\n")

        for index, line in enumerate(section_list):
            dag_text = re.search('<i class="date">(.*?)</i>', line, re.DOTALL)
            if dag_text:
                dag_text = dag_text.group(1)
                pickup_date = convert_date(dag_text)
                pickup_type = None
                soort_afval = re.search('<i>(.*?)</i>', section_list[index + 1], re.DOTALL).group(1)

                if "Groente" in soort_afval:
                    pickup_type = "Groente"
                    item_name = "Afval_gft"
                elif "Plastic" in soort_afval:
                    pickup_type = "Plastic"
                    item_name = "Afval_pmd"
                elif "Papier" in soort_afval:
                    pickup_type = "Papier"
                    item_name = "Afval_papier"
                elif "Chemisch" in soort_afval:
                    pickup_type = "Chemisch"
                    item_name = "Afval_kga"
                elif "Restafval" in soort_afval:
                    pickup_type = "Restafval"
                    item_name = "Afval_rest"

                send_trash_pickup_notifications.log.debug("{}: {}".format(pickup_type, pickup_date))
                events.sendCommand(item_name, pickup_date.toString())
                if pickup_type and DateTimeType().zonedDateTime.toLocalDate() == pickup_date.toLocalDate():
                    Telegram.sendTelegram("domoticahajerstijn", "Afval notificatie: {}".format(pickup_type))
    else:
        send_trash_pickup_notifications.log.warn("Communication failure")
2 Likes

:grinning: Truely a great example. That is super!
For sure a number of lines which would have taken me a long time to figure out.

Going to read and install the needed preconditions to test the script. Valuable reference design.

Thanks!

1 Like

Think I need help.

Tried to follow the installation procedure of 5jver as exact as possible (although confusing to do 2 procedures combined), I must have done something wrong.

The javascript part is working. The “hello_world.js” works correctly and gives a message very 1 seconds (removed it again to de-clutter the logging)

The for python version I get:

2020-01-03 18:33:11.845 [INFO ] [me.core.service.AbstractWatchService] - ScriptEngine for py not available
2020-01-03 18:33:12.850 [INFO ] [me.core.service.AbstractWatchService] - ScriptEngine for py not available

2020-01-03 18:26:26.886 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'velbus.rules'
2020-01-03 18:26:29.051 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'aqara.rules'
2020-01-03 18:26:29.899 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'sonsos.rules'
2020-01-03 18:26:30.064 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'telegram.rules'
2020-01-03 18:26:30.339 [INFO ] [thome.model.lsp.internal.ModelServer] - Started Language Server Protocol (LSP) service on port 5007
2020-01-03 18:26:33.215 [INFO ] [.dashboard.internal.DashboardService] - Started Dashboard at http://192.168.3.130:8080
2020-01-03 18:26:33.220 [INFO ] [.dashboard.internal.DashboardService] - Started Dashboard at https://192.168.3.130:8443
2020-01-03 18:26:39.964 [INFO ] [ternal.dhcp.DHCPPacketListenerServer] - DHCP request packet listener online
2020-01-03 18:26:40.421 [INFO ] [b.core.service.AbstractActiveService] - NetworkHealth Refresh Service has been started
2020-01-03 18:26:41.451 [DEBUG] [.AutomationResourceBundlesEventQueue] - Process bundle event 2, for automation bundle 'org.openhab.core.automation' 
2020-01-03 18:26:41.465 [DEBUG] [vider.AbstractResourceBundleProvider] - Parse rules from bundle 'org.openhab.core.automation' 
2020-01-03 18:26:41.465 [DEBUG] [.AutomationResourceBundlesEventQueue] - Process bundle event 32, for automation bundle 'org.openhab.core.automation.module.script.rulesupport' 
2020-01-03 18:26:41.469 [DEBUG] [vider.AbstractResourceBundleProvider] - Parse rules from bundle 'org.openhab.core.automation.module.script.rulesupport' 
2020-01-03 18:26:41.958 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added CoreModuleHandlerFactory
2020-01-03 18:26:41.962 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added EphemerisModuleHandlerFactory
2020-01-03 18:26:41.965 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added TimerModuleHandlerFactory
2020-01-03 18:26:41.968 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added AnnotatedActionModuleTypeProvider
2020-01-03 18:26:42.043 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added AnnotatedThingActionModuleTypeProvider
2020-01-03 18:26:42.124 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added MediaModuleHandlerFactory
2020-01-03 18:26:42.253 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Added GenericScriptEngineFactory
2020-01-03 18:26:42.262 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - ScriptEngineFactory details for Oracle Nashorn (1.8.0_152): supports ECMAScript (ECMA - 262 Edition 5.1) with file extensions [js], names [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript], and mimetypes [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
2020-01-03 18:26:42.266 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Added NashornScriptEngineFactory
2020-01-03 18:26:42.270 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - ScriptEngineFactory details for Oracle Nashorn (1.8.0_152): supports ECMAScript (ECMA - 262 Edition 5.1) with file extensions [js], names [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript], and mimetypes [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
2020-01-03 18:26:42.276 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Removed GenericScriptEngineFactory
2020-01-03 18:26:42.280 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Removed NashornScriptEngineFactory
2020-01-03 18:26:42.334 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Added GenericScriptEngineFactory
2020-01-03 18:26:42.338 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - ScriptEngineFactory details for Oracle Nashorn (1.8.0_152): supports ECMAScript (ECMA - 262 Edition 5.1) with file extensions [js], names [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript], and mimetypes [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
2020-01-03 18:26:42.341 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Added NashornScriptEngineFactory
2020-01-03 18:26:42.345 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - ScriptEngineFactory details for Oracle Nashorn (1.8.0_152): supports ECMAScript (ECMA - 262 Edition 5.1) with file extensions [js], names [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript], and mimetypes [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
2020-01-03 18:26:42.350 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added ScriptModuleHandlerFactory
2020-01-03 18:26:42.445 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added ScriptedCustomModuleHandlerFactory
2020-01-03 18:26:42.467 [DEBUG] [e.automation.internal.RuleEngineImpl] - ModuleHandlerFactory added ScriptedPrivateModuleHandlerFactory
2020-01-03 18:26:51.400 [INFO ] [.transport.mqtt.MqttBrokerConnection] - Starting MQTT broker connection to '192.168.3.130' with clientid 6a423c79-94ee-48b8-8f5b-d67d43d2837f
2020-01-03 18:26:51.521 [INFO ] [.transport.mqtt.MqttBrokerConnection] - Starting MQTT broker connection to '192.168.3.130' with clientid 07e7727b-1a7e-4a76-bb93-49983cb7f54d
2020-01-03 18:26:52.324 [INFO ] [thome.model.script.VelBus init rules] - finished
2020-01-03 18:26:52.335 [INFO ] [me.model.script.Aqara Initiate.rules] - finished
2020-01-03 18:26:53.062 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'velbus.rules'
2020-01-03 18:26:54.486 [INFO ] [openhab.ui.paper.internal.PaperUIApp] - Started Paper UI at /paperui
2020-01-03 18:27:04.720 [INFO ] [thome.model.script.VelBus init rules] - finished
2020-01-03 18:27:07.530 [INFO ] [me.core.service.AbstractWatchService] - Loading script 'javascript/core/000_startup_delay.js'
2020-01-03 18:27:10.168 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Added ScriptEngine for language 'js' with identifier: file:/etc/openhab2/automation/jsr223/javascript/core/000_startup_delay.js
2020-01-03 18:27:10.379 [INFO ] [jsr223.javascript.core.startup_delay] - Checking for initialized context
2020-01-03 18:27:10.459 [INFO ] [jsr223.javascript.core.startup_delay] - Context initialized... waiting 30s before allowing scripts to load
2020-01-03 18:27:40.517 [INFO ] [jsr223.javascript.core.startup_delay] - Complete

What went wrong?

Grtz Matthijs

Which version of OH are you running and which jar file from the post Scott linked to did you install? What’s the permissions on that jar file after you copied it into your addons folder.

OH 2.5.0-M5-1

JAR:

/usr/share/openhab2/addons#
-rwxr-xr-x 1 openhab openhab   112191 Aug 15 08:22 org.openhab.binding.velbus-2.5.0-SNAPSHOT13d.jar
-rwxr-xr-x 1 openhab openhab 37403000 Jan  3 17:19 org.openhab.core.automation.module.script.scriptenginefactory.jython-2.5.0-SNAPSHOT.jar

Hmmmm, I’m not certain that the jar will work on a version of OH that old. You may need to move to 2.5 M6 or preferably 2.5.0 release.

oke. Last time I tried to update, I got the “famous” mqtt error (and while figuring that out, killed my installation). Will make a backup tonight and update.

5iver indeed states: Have OH 2.5 (S1778) or newer

So lets try.

Yes, you will need 2.5 in order to get a last minute PR for openHAB core that let’s this bundle work.

Progres!

Almost there. Upgrade to 2.5.0 worked. Jython is running and the script is loaded.

BUT, there is a small bug in the script . See below. So now my question is, how can I run the script without setting the crontab every time. What is the “commandline way” to run these scripts?

# File "/etc/openhab2/automation/lib/python/core/log.py", line 51, in wrapper
# return fn(*args, **kwargs)
# File "<script>", line 91, in send_trash_pickup_notifications
# AttributeError: 'java.time.ZonedDateTime' object has no attribute 'zonedDateTime'

So something is wrong with this part of the code and I like to debug / test various alternatives I found online.

def convert_date(day_text):
    day_list = day_text.split(" ")
    new_date = DateTimeType().zonedDateTime.withMonth(monthToNum(day_list[2])).withDayOfMonth(int(day_list[1])).withHour(0).withMinute(0).withSecond(0).withNano(0)
    return new_date

How to proceed?
(of course the bug fix is appreciated too, but the general question remains: how to run scripts on demand).

Thanks,
Matthijs

It’s quite easy to test scripts. Here are 3 options…

  1. Add a “System started” trigger, which will trigger the rule when the file is saved. This is how I tested this rule before posting.
@when("System started")
  1. Rather than trigger the rule, just call the rule function in the script. This can be tricky, depending on the event object that is expected. Fortunately, the event object for cron triggers and startup triggers are None.
send_trash_pickup_notifications(None)
  1. Just create another script and add the body of the rule function, or portions of it. I have several test scripts with commented out tests that I am constantly using when working on things.

To debug this, you will probably want to wrap things in try/except. This is done for you with the rule decorator, along with a traceback, using the core.log.log_traceback decorator, which comes in handy during testing.

As for the error, I’ve made a small fix in the 4th line from the end that I had commented out during testing because I don’t use Telegram. convert_date returns a ZonedDateTime, so pickup_date is a ZonedDateTime, not a DateTimeType.

In addition to the three options Scott provided I have a couple more.

  1. Click the play button next to the Rule in PaperUI. As with running the function from another script or System started, it may not work if you are expecting something specific in the event object. I suspect this can be done from the REST API docs and the Karaf console too.

  2. Manually command or update the triggering item using another rule, a script, the REST API docs, or the Karaf console. This approach will be the closest to how the rule will actually run in real life for Item or Member of triggered rules.

Guess what? Python is a scripting language used in programming.

Speaking from experience, it takes time and dedication to learn Python. I took the time myself to learn it in the past 18 months.

If you cannot follow simple programming. you could not implement your idea for this thread anyway.

Think I will be alright :slight_smile: Both options are working fine now. Got some very good help in this thread.

Controlled pretty complex machines over the last years with python. So I do have some experience, but not a programming background.

CU,
Matthijs

1 Like

Come on, Bruce! Your entire response here is harsh, uncalled for, and completely unhelpful! Definitely not the attitude that is encouraged here. The OP obviously has Python experience and had written a solution in Python that solved his needs. I only provided an example of using scripted automation w/ Jython so that he could learn from it.

1 Like

@matthijsfh, your post inspired me to solve the problem slightly differently. I made a new post here. Unfortunately, this does not work for your postal code.

@ArdKuijpers Nice one!

And very good to point out the option to develop parts of code in an environment with a proper debugger (and code completion). Having an IDE with those features helped me a lot with the initial code I made (outside OH jython). Fixing the small bug in the 5iver code did costs me relative much time due to the absence of a debugger.

Best of both worlds.

Grtz

It does work with a modified version of of the get_pickupdates function. Look here.

Yep, my fault. It DOES work for my address.

You first get at “BagID” (what is that?) then the actual data. That is pretty cool.

Great.