Jython is not deprecated

Again, thanks a lot for all of your work on keeping Jython going. It may mean that I transferred half my scripts to HABApp for nothing, but that’s ok. HABApp has sort of stalled in the last 6 months from what I can tell. But, it’s good to have options!

What makes you think that? Development is still going strong but updates are happening in the underlying libraries. Are you missing features or experiencing problems which need to be solved?

2 Likes

I moved from Jython JSR223 to HabApp about a year ago and I do not regret it:

  • Good support, lifecycle, and stability.
  • Support for newest external libraries
  • Debugging directly in VSCode.
  • There is some paradigm change, though I developed my migration layer, I can keep more or less code as before.
1 Like

From my perspective, I want to avoid an additional component like HabApp to deploy.

Another reason is that you can’t do everything as you can do with a native Jython binding like openhab actions.

I use docker for my home automation stack, so HabApp is just an additional service in Portainer.

Which actions do you particularly mean, or what limitations do you see? I had to implement some action functionality, though it was easy.

I already have 33 container running on my setup

My project

so, I agree, one additional make no difference. But why, if I can do everything what I want with a pure openhab / jython solution.

another reason is that HabApp is saying what I can do with openhab. Jython binding let me do everything in openhab what is available via java api. So there is no limitation in functionality.

I use also the pushover & telegram binding and transformation service/map in my rules, which is not available in HabApp.

Also things like direct channel access or functionality like group members I can’t find.

Persistence min / max is missing too…

to make it short, I want to use all openhab functionality in my openhab rules.

My python rules

My helper lib

2 Likes

For pushover there are native python implementations available but in reality it’s just a simple http request and HABApp provides a native wrapper for that. For me the pushover binding is unusable because it does not expose the monospace parameter. Without it reading tracebacks is really hard on mobile.
All map transformations are available in HABApp.

I can make the same argument that Jython is saying what I can do with python.
It’s old, deprecated python 2.7 with sometimes quirky, java specific and surprising behavior. With HABApp I can use the latest language features and up to date libraries.
I spend more time writing python code than interfacing the openHAB APIs so for me that’s more important.

While some of your points are valid for most if not all points there are easy workarounds.
Since you are a programming expert with java and openHAB experience Jython is the better fit for you. But I’d argue that for the majority of people that want to write rules in Python HABApp is the better fit because it makes the majority of things just that much easier. Spinning up an additional service is a cheap price to pay.

influxdb.yml:

influxdb:
    host: 192.168.3.140
    port: 8086
    username: openhab
    password: .Techno.
    database: openhab_db

persistance_data.py

# Some other stuff
#
# HABApp:
#   reloads on:
#    - params/influxdb.yml

from personal.log_stack import getLogger
log = getLogger("persitat_data")

from influxdb import InfluxDBClient
import yaml
from datetime import timedelta 
import os
from pathlib import Path
from personal.migration_helper import RuleJSR
from datetime import datetime  

CONFIG_FILE = Path(os.environ.get('HABAPP_CONF')) /  "params/influxdb.yml"

class InfluxDBHelper(RuleJSR):
    
    def __init__(self, config_file=CONFIG_FILE):
        super().__init__()        
        self.client = None
        self.config_file = config_file
        self.load_config()


    def on_rule_removed(self): 
        self.close()

    def close(self):
        """Close the InfluxDB connection."""
        self.client.close()

    def load_config(self):
        if self.client:
            self.client.close()
        with open(self.config_file, 'r', encoding='utf-8') as file:
            config = yaml.safe_load(file)
            config = config['influxdb']
            self.client = InfluxDBClient(
                host=config['host'], 
                port=config['port'], 
                username=config['username'], 
                password=config['password'], 
                database=config['database']
            )
            

    def get_delta_since(self, item: str, period: timedelta) -> float:

        # Convert the timedelta to an InfluxDB-compatible time string
        minutes = int(period.total_seconds() / 60)
        
        # Define the query
        query = f'SELECT last("value") - first("value") AS "delta" FROM "{item}" WHERE time >= now() - {minutes}m'

        # Execute the query
        result = self.client.query(query)

        # Process the result
        points = list(result.get_points())
        
        if not points:
            return 0
        else:
            return points[0]['delta']

    def get_average_since(self, item: str, period: timedelta) -> float:

        # Convert the timedelta to an InfluxDB-compatible time string
        minutes = int(period.total_seconds() / 60)
        
        # Define the query to get the average
        query = f'SELECT MEAN("value") AS "average" FROM "{item}" WHERE time >= now() - {minutes}m'
        
        # Execute the query
        result = self.client.query(query)

        # Process the result
        points = list(result.get_points())
        
        if not points or 'average' not in points[0]:
            return 0
        else:
            return points[0]['average']

    def get_min_since(self, item: str, period: timedelta) -> float:

        # Convert the timedelta to an InfluxDB-compatible time string
        minutes = int(period.total_seconds() / 60)
        
        # Define the query to get the minimum
        query = f'SELECT MIN("value") AS "min_value" FROM "{item}" WHERE time >= now() - {minutes}m'
        
        # Execute the query
        result = self.client.query(query)

        # Process the result
        points = list(result.get_points())
        
        if not points or 'min_value' not in points[0]:
            return 0
        else:
            return points[0]['min_value']

    def get_max_since(self, item: str, period: timedelta, at: datetime = datetime.now()) -> float:
        """
        Get the maximum value of 'item' since 'period' from the time 'at'.
        
        :param item: The item to query in the database.
        :param period: The timedelta from 'at' for which to calculate the maximum.
        :param at: The end time for the query, defaults to the current time.
        :return: The maximum value or 0 if no data is found.
        """
        # Convert the timedelta to an InfluxDB-compatible time string
        minutes = int(period.total_seconds() / 60)
        
        # Format the 'at' datetime to a string that InfluxDB can understand
        at_formatted = at.strftime('%Y-%m-%dT%H:%M:%SZ')
        
        # Define the query to get the maximum, using 'at' instead of 'now()'
        query = f'SELECT MAX("value") AS "max_value" FROM "{item}" WHERE time >= \'{at_formatted}\' - {minutes}m AND time <= \'{at_formatted}\''
        
        # Execute the query
        result = self.client.query(query)

        # Process the result
        points = list(result.get_points())
        
        if not points or 'max_value' not in points[0]:
            return 0
        else:
            return points[0]['max_value']
            
persistance = InfluxDBHelper()

I just looked at your Jython rules and see that you using the same way as I used the Jython rules, setting self.triggers, rather than using the other decoration methods:
image
For this, I have written wrappers for more or less lift and shift to HabApp:
My rules look like this:

from personal.log_stack import getLogger
log = getLogger("low_bat")

from HABApp.openhab.items import GroupItem

from HABApp.openhab.events.item_events import GroupStateChangedEvent
from HABApp.openhab.events.item_events import ItemStateUpdatedEvent



import personal.migration_helper
import importlib
importlib.reload(personal.migration_helper)  

from personal.migration_helper import RuleJSR

import importlib
import personal.timers
importlib.reload(personal.timers)

import personal.pim
importlib.reload(personal.pim)   
from personal.pim import *  

from personal.timers import SingleTimer

message = Message(EMAIL+PUSH) 


triggers = [] 
#triggers.append((TimeTriggerEvent("every day 12:00", timedelta(hours=11)+timedelta(minutes=35), timedelta(days=1)), "CHECKSTATUS"))
triggers.append((ItemStateUpdatedEvent("DebugTrigger", "ON"), "CHECKSTATUS"))  
triggers.append((GroupStateChangedEvent("gLowBat", None, "ON", "OFF"), "CHECKSTATUS"))  
triggers.append((GroupStateChangedEvent("gLowBatLev", None, None, None), "CHECKSTATUS"))  


class lowBattRule(RuleJSR):    

    def __init__(self):
        super().__init__()  
 
        self.set_triggers(self.execute, triggers)       
        self.check_battery_timer = SingleTimer("SIN_Check_Batt", "12:00:00", lambda: self.check_batteries_status()) 

    def check_batteries_status(self):
        def check_battery_status_in_group(group, messages):
            for member in group.members:
                # Store the result of member.get_value() in a variable
                member_value = member.get_value()

                # If the member is a group, recursively check its members.
                if isinstance(member, GroupItem):
                    check_battery_status_in_group(member, messages)
                # If it's an item and its state is "ON", add a message about low battery status.
                elif isinstance(member_value, str) and member_value == "ON":
                    messages.append(f"{member.label} hat niedrigen Batteriestand")
                # If it's an item with a numeric value below 10, add a message about the battery level.
                elif isinstance(member_value, (int, float)) and member_value < 30:
                    messages.append(f"{member.label} hat {member_value}%")

        messages = []
        check_battery_status_in_group(GroupItem.get_item('gLowBat'), messages)
        check_battery_status_in_group(GroupItem.get_item('gLowBatLev'), messages)

        # If there are any messages, concatenate them and send as one message.
        if messages:
            final_message = "\n".join(messages)
            message.send("openhab", final_message)
 

    def execute(self, event):

        if event.action == "CHECKSTATUS":
            self.check_batteries_status()
 
lowBattRule()

If you wish I can share all the helper libraries.

I have further looked at your rules and they are massive. Maintaining this without having the possibility to debug might be a nightmare.

2 Likes
2 Likes

@holger_hees s/persistance/persistence/

1 Like

Pleas check the new helper libraries from pythonscripting binding to confirm if there is something you miss or you would recommend to add from your helper libraries.

thanks and it is fixed in the new release