Is there a Simpler way to split a string

I’m trying to split a JSON file from my Enphase Micro inverters into individual items and would like help doing it in the easiest way.
A cut down output from querying the device with CURL gives this:

      [
      {
        "serialNumber": "1234567890",
        "lastReportDate": 0,
        "lastReportWatts": 0,
        "maxReportWatts": 0
      },
      {
        "serialNumber": "1234567891",
        "lastReportDate": 1581575373,
        "lastReportWatts": 198,
        "maxReportWatts": 198
      },
      {
        "serialNumber": "1234567892",
        "lastReportDate": 1582058813,
        "lastReportWatts": 139,
        "maxReportWatts": 141
      },
      {
        "serialNumber": "1234567893",
        "lastReportDate": 1582010047,
        "lastReportWatts": 0,
        "maxReportWatts": 4
      }
    ]
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed

      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    100    90  100    90    0     0  30000      0 --:--:-- --:--:-- --:--:-- 30000

    100  5964  100  5964    0     0   145k      0 --:--:-- --:--:-- --:--:--  145k
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed

      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    100    90  100    90    0     0  30000      0 --:--:-- --:--:-- --:--:-- 30000

    100  5964  100  5964    0     0   145k      0 --:--:-- --:--:-- --:--:--  145k  

which I can split into individual serial numbers using

 var Panel1Split1 = MyString.toString.split('"serialNumber": "1234567891",').get(1)
 var Panel1Split2 = Panel1Split1.toString.split('}').get(0)

Which gives me a string similar to this:

        "lastReportDate": 1581575373,
        "lastReportWatts": 198,
        "maxReportWatts": 198

Once I’ve further processed the individual values, the rule is getting very cumbersome just for one panel with its individual values extracted from that string. Once all my panels are in the rule it will be very long.

Is there a simpler way of doing this?

I tried the Beta Enphase Binding but it doesn’t work with my System due to not being able to change the username

Did you consider using JSONPATH to parse the response and iterate over the elements of the json array?

1 Like

I tried it but couldn’t work out how to pick out a serial number with it and then get one of the fields out. I was thinking along the lines of searching through the string, matching a serial number and then returning the lastReportDate value, but I have absolutely no idea how to return what I need.
Its easy if its formatted like this

 {"Time":"2020-02-19T04:00:53","Uptime":"2T03:50:33","UptimeSec":186633,"Heap":28,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":1,"POWER1":"ON","POWER2":"ON","POWER3":"ON","POWER4":"ON","POWER5":"ON","POWER6":"ON","POWER7":"ON","Wifi":{"AP":1,"SSId":"XXXXXXX","BSSId":"xx:xx:xx:xx:xx:xx","Channel":1,"RSSI":82,"Signal":-59,"LinkCount":1,"Downtime":"0T00:01:41"}}

which is a Tasmota Telemetry message, and I could use this to get the information after splitting the string in the examples above but for 54 individual values its fast becoming cumbersome

You can use websites like this to help determine the path of each parameter and how to extract them in a manner similar to this, or this.

I am doing something similiar for my many envriomental sensors. First I assign the curl output to a string item.

API output item

String MobileAlertsAPI "Mobile-Alerts API query" <text> { channel="exec:command:mobilealertsAPI:output" } // run every 5mins. See Thing

Then I have one rule which extracts the data for each sensor in turn when the curl output changed. It only updates the associated item if the value has changed.

eg code for my hot water sensor

        //MobileAlertsHotWaterTemperature 0915C9862CBF
        //Update item if sensor temperature changed
        val String newHotWaterTemperature = transform("JS", "getProHotWaterTemperature.js", MobileAlertsAPI.state.toString)
        if (newHotWaterTemperature !== null) { //Check transform worked eg after battery change change
            val String previousHotWaterTemperature = MobileAlertsHotWaterTemperature.state.toString
            if (newHotWaterTemperature != previousHotWaterTemperature) {
                MobileAlertsHotWaterTemperature.sendCommand(newHotWaterTemperature)
                Thread::sleep(1000)
                logInfo("Mobile Alerts API" , 'Hot water temperature = ' + MobileAlertsHotWaterTemperature.state.toString + ' °C')
            }
        }
        else {
            logWarn("Mobile Alerts API" , "WARNING: API transform failed for Hot Water Temperature")
        }
   

The js transofrm looks like this

(function(i) {
    var re = new RegExp('device id here[\\s\\S]*?parameter id here[\\s\\S]*?([0-9]+.[0-9]+|-[0-9]+.[0-9]+)');
    var out = i.match(re)[1];
    return parseFloat(out);
})(input)

You can use sites like regex101 to check and test regex expressions (remove the escape \ for 101)

The regex identifies each item via its id and parameter to extract.

I also extract the epoch time and calculate how long has elapsed since the sensor was last seen. The rule issues notifications if its been too long.

Thanks for the replies everyone. The rule now runs to 136 lines using my original example, I’ll see what I can get to work from the suggestions. .

@pahansen unfortunately JSONPATHFinder.com says it isn’t JSON and doesn’t work
@m4rk JS may be the way to work this

I’ll look into “for each” loops too as this may offer a way of simplifying the code as essentially its doing a similar set of operations 18 times.

Another option is to use the new rule engine, scripted automation, and the helper libraries, which has a json module built in. Let me know if you need help or more information.

That’s a language I know absolutely nothing about, but I’ll give it some thought :slight_smile:

1 Like

Well, you didn’t know anything about the rules DSL when you started with OH either :stuck_out_tongue_winking_eye:! Python is very easy to pickup and there is a lot of information available on the Internet, books, courses, etc. The helper libraries are set up to make it easier to use than the rules DSL. It’s also going to be mainstream for OH 3.0, so you can wait for it or get started now!

1 Like

The trouble is I have a very complicated and extensive system developed since OH 1.6, complete with 350K of rules spread over 23 files written in the current rules DSL. That’ll take considerable work and time to translate into a language I know nothing about. My system runs everything from waking us up through to controlling the temperature, failure would be very inconvenient, and I outgrew a Pi2 and Pi3 combination months ago. That together with the inevitable things that don’t work as expected when they are rewritten is daunting on a stable system. Whilst I have a long standing background in IT OpenHAB is the first thing I’ve programmed.
Is this a mature language or still evolving? I’m keen to avoid the “I’ve updated it does anything work as expected” scenario
Is it pure Python and can it run Python scripts inside OH rather than from a command prompt? Perhaps as a first thing I could experiment with the simple Python Script I hacked together to read a couple of sensors.

Do you mean 350K LOC?! If so, that has got to be a record! I went from 6000+ LOC in the rules DSL to <2000 LOC using Jython and the helper libraries.

Yes, Jython is mature and very stable. I recommend using 2.7.0, but 2.7.2 is about to be released and 2.7.2b3 has been working very well. There has been work on a v3 and discussion of continuing that effort after the release of 2.7.2.

No.

Yes, this is the only option.

If you post a DSL rule or your script, I will happily migrate it for you to get things started!

That 350K is the uncompressed file size.

I’m busy for the rest of the day, but here’s a simple rule to start with that’s running right now. Let me know if you’d prefer it in txt format as the formatting seems to be messed up. Thanks for taking a look, may as well see what’s in store!

rule "Office Heatpump Control loop"
when Item OfficeHeatpump changed or Item OfficeTemperature changed or Item OfficeCoolingTemperature changed or Item OfficeHeatingTemperature changed or Item OfficeCoolingMode changed then {
KOT = OfficeTemperature.state as Number
var HSP = OfficeHeatingTemperature.state as Number
var CSP = OfficeCoolingTemperature.state as Number
if (OfficeHeatpump.state == ON)	{	
	if (OfficeCoolingMode.state == ON)	{
		if (OfficeHeatpumpMode.state == "cool"){} else {OfficeHeatpumpMode.sendCommand('cool')}
		//cooling code
		if (OfficeHPRequestedTemp === NULL) {OfficeHPRequestedTemp.sendCommand(18.0)}
		switch(KOT)	{
			case KOT > CSP + 0.7	:    {	if (OfficeHPFanSpeed.state == "auto") {} else{
																	OfficeHPFanSpeed.sendCommand("auto")
																}
																if (OfficeHPPower.state == ON) {} else {
																	OfficeHPPower.sendCommand(ON) 
																}
																if (OfficeHPRequestedTemp.state == 18.0){} else {
													//				logInfo("KOR", "need 18.0 OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
																	OfficeHPRequestedTemp.sendCommand(18.0)
																}
															}
			case KOT > CSP + 0.3 && KOT <= CSP + 0.6	:	{	if (OfficeHPFanSpeed.state == "auto") {} else{
																	OfficeHPFanSpeed.sendCommand("auto")
																}
																if (OfficeHPPower.state == ON) {} else {
																	OfficeHPPower.sendCommand(ON)
																}
																if (OfficeHPRequestedTemp.state == CoolIRTemp){} else {
												//					logInfo("KOR", "need CoolIRTemp OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
																	OfficeHPRequestedTemp.sendCommand(CoolIRTemp)
																}
															}	
			case KOT > CSP && KOT <=CSP + 0.2			: 	{	if (OfficeHPFanSpeed.state == "min") {} else{
																	OfficeHPFanSpeed.sendCommand("min")
																}
																if (OfficeHPPower.state == ON) {} else {
																	OfficeHPPower.sendCommand(ON)
																}
																if (OfficeHPRequestedTemp.state == CoolIRTemp){} else {
												///					logInfo("KOR", "need CoolIRTemp OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
																	OfficeHPRequestedTemp.sendCommand(CoolIRTemp)
																}
															}	
			case KOT < CSP - 0.1						:	if (OfficeHPPower.state == OFF) {} else {
																OfficeHPPower.sendCommand(OFF)
																}
		}
	}
	if (OfficeCoolingMode.state == OFF)	{	//heating code
		if (OfficeHeatpumpMode.state == "heat") {} else {OfficeHeatpumpMode.sendCommand("heat")}
		if (OfficeHPRequestedTemp === NULL) {OfficeHPRequestedTemp.sendCommand(28.0)}
		switch(KOT)	{
			case KOT < HSP - 1.1						:	{	if (OfficeHPFanSpeed.state == "auto") {} else{
																OfficeHPFanSpeed.sendCommand("auto")
													//			logInfo("KOR", "need auto OfficeHPFanSpeed is "+OfficeHPFanSpeed.state)
																}
																if (OfficeHPPower.state == ON) {} else { OfficeHPPower.sendCommand(ON)
													//				logInfo("KOR", "need on top OfficeHPPower is "+OfficeHPPower.state)
																	}
																if (OfficeHPRequestedTemp.state == 28.0) {} else {
													//				logInfo("KOR", "need 28 OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
																	OfficeHPRequestedTemp.sendCommand(28)
																	}
															}	
			case KOT < HSP - 0.2 && KOT > HSP - 0.9	:	{	if (OfficeHPFanSpeed.state == "min") {} else {
																	OfficeHPFanSpeed.sendCommand("min")
																	//logInfo("KOR", "need min OfficeHPFanSpeed is "+OfficeHPFanSpeed.state)
																	}
																if (OfficeHPRequestedTemp.state == CoolIRTemp){} else {
													//				logInfo("KOR", "need CoolIRTemp OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
																	OfficeHPRequestedTemp.sendCommand(CoolIRTemp)
																	}
																if (OfficeHPPower.state == ON) {} else {
																	OfficeHPPower.sendCommand(ON)}
																	}
			case KOT > HSP								:		if (OfficeHPPower.state == OFF){} else { OfficeHPPower.sendCommand(OFF) }
			}
		}
	} else if (OfficeHPPower.state == OFF) {} else {OfficeHPPower.sendCommand(OFF)}
}
end

This rule could be cleaned up, but I’ve tried to show a side-by-side comparison with what you had. I’ve tried to keep it a pretty straight conversion, but I corrected a couple issues with your rule. You did not include a definition for CoolIRTemp, which I assume is a global variable in the DSL rule. For this Jython version, I made it a DecimalType.

from core.rules import rule
from core.triggers import when

COOL_IR_TEMP = DecimalType(55)

#rule "Office Heatpump Control loop"
@rule("Office Heatpump Control loop")
#when Item OfficeHeatpump changed or Item OfficeTemperature changed or Item OfficeCoolingTemperature changed or Item OfficeHeatingTemperature changed or Item OfficeCoolingMode changed then {
@when("Item OfficeHeatpump changed")
@when("Item OfficeTemperature changed")
@when("Item OfficeCoolingTemperature changed")
@when("Item OfficeHeatingTemperature changed")
@when("Item OfficeCoolingMode changed")
def office_heatpump_control(event):
    #KOT = OfficeTemperature.state as Number
    KOT = items["OfficeTemperature"].doubleValue()
    #var HSP = OfficeHeatingTemperature.state as Number
    HSP = items["OfficeHeatingTemperature"].doubleValue()
    #var CSP = OfficeCoolingTemperature.state as Number
    CSP = items["OfficeCoolingTemperature"].doubleValue()
    #if (OfficeHeatpump.state == ON)	{	
    if items["OfficeHeatpump"] == ON:
        #if (OfficeCoolingMode.state == ON)	{
        if items["OfficeCoolingMode"] == ON:
            #if (OfficeHeatpumpMode.state == "cool"){} else {OfficeHeatpumpMode.sendCommand('cool')}
            if items["OfficeHeatpumpMode"] != StringType("cool"):
                events.sendCommand("OfficeHeatpumpMode", "cool")
            #//cooling code
            #if (OfficeHPRequestedTemp === NULL) {OfficeHPRequestedTemp.sendCommand(18.0)}
            # as written, this is never possible, so you probably meant to do this
            if isinstance(items["OfficeHPRequestedTemp"], UnDefType):
                events.sendCommand("OfficeHPRequestedTemp", "18.0")
            #switch(KOT)	{
            # switch/case does not exist in Python
            #    case KOT > CSP + 0.7	:    {	if (OfficeHPFanSpeed.state == "auto") {} else{
            #                                                            OfficeHPFanSpeed.sendCommand("auto")
            #                                                        }
            #                                                        if (OfficeHPPower.state == ON) {} else {
            #                                                            OfficeHPPower.sendCommand(ON) 
            #                                                        }
            #                                                        if (OfficeHPRequestedTemp.state == 18.0){} else {
            #                                            //				logInfo("KOR", "need 18.0 OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
            #                                                            OfficeHPRequestedTemp.sendCommand(18.0)
            #                                                        }
            #                                                    }
            if KOT > CSP + 0.7:
                if items["OfficeHPFanSpeed"] != StringType("auto"):
                    events.sendCommand("OfficeHPFanSpeed", "auto")
                if items["OfficeHPPower"] != ON:
                    events.sendCommand("OfficeHPPower", "ON")
                if items["OfficeHPRequestedTemp"] != DecimalType(18.0):
                    #office_heatpump_control.log.info("need 18.0 OfficeHPRequestedTemp is {}".format(items["OfficeHPRequestedTemp"]))
                    events.sendCommand("OfficeHPRequestedTemp", "18.0")
            #    case KOT > CSP + 0.3 && KOT <= CSP + 0.6	:	{	if (OfficeHPFanSpeed.state == "auto") {} else{
            #                                                            OfficeHPFanSpeed.sendCommand("auto")
            #                                                        }
            #                                                        if (OfficeHPPower.state == ON) {} else {
            #                                                            OfficeHPPower.sendCommand(ON)
            #                                                        }
            #                                                        if (OfficeHPRequestedTemp.state == CoolIRTemp){} else {
            #                                        //					logInfo("KOR", "need CoolIRTemp OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
            #                                                            OfficeHPRequestedTemp.sendCommand(CoolIRTemp)
            #                                                        }
            #                                                    }
            elif KOT > CSP + 0.3 and KOT <= CSP + 0.6:
                if items["OfficeHPFanSpeed"] != "auto":
                    events.sendCommand("OfficeHPFanSpeed", "auto")
                if items["OfficeHPPower"] != ON:
                    events.sendCommand("OfficeHPPower", "ON")
                if items["OfficeHPRequestedTemp"] != COOL_IR_TEMP:
                    #office_heatpump_control.log.info("need COOL_IR_TEMP OfficeHPRequestedTemp is {}".format(items"[OfficeHPRequestedTemp"]))
                    events.sendCommand("OfficeHPRequestedTemp", COOL_IR_TEMP.toString())
            #    case KOT > CSP && KOT <=CSP + 0.2			: 	{	if (OfficeHPFanSpeed.state == "min") {} else{
            #                                                            OfficeHPFanSpeed.sendCommand("min")
            #                                                        }
            #                                                        if (OfficeHPPower.state == ON) {} else {
            #                                                            OfficeHPPower.sendCommand(ON)
            #                                                        }
            #                                                        if (OfficeHPRequestedTemp.state == CoolIRTemp){} else {
            #                                        ///					logInfo("KOR", "need CoolIRTemp OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
            #                                                            OfficeHPRequestedTemp.sendCommand(CoolIRTemp)
            #                                                        }
            #                                                    }
            elif KOT > CSP and KOT <= CSP + 0.2:
                if items["OfficeHPFanSpeed"] != "min":
                    events.sendCommand("OfficeHPFanSpeed", "min")
                if items["OfficeHPPower"] != ON:
                    events.sendCommand("OfficeHPPower", "ON")
                if items["OfficeHPRequestedTemp"] != COOL_IR_TEMP:
                    #office_heatpump_control.log.info("need COOL_IR_TEMP OfficeHPRequestedTemp is {}".format(items"[OfficeHPRequestedTemp"]))
                    events.sendCommand("OfficeHPRequestedTemp", COOL_IR_TEMP.toString())
            #    case KOT < CSP - 0.1						:	if (OfficeHPPower.state == OFF) {} else {
            #                                                        OfficeHPPower.sendCommand(OFF)
            #                                                        }
            elif KOT < CSP - 0.1:
                if items["OfficeHPPower"] != OFF:
                    events.sendCommand("OfficeHPPower", "OFF")
            #}
        #}
        #if (OfficeCoolingMode.state == OFF)	{	//heating code
        elif items["OfficeCoolingMode"] == OFF:# heating code
            #if (OfficeHeatpumpMode.state == "heat") {} else {OfficeHeatpumpMode.sendCommand("heat")}
            if items["OfficeHeatpumpMode"] != StringType("heat"):
                events.sendCommand("OfficeHeatpumpMode", "heat")
            #if (OfficeHPRequestedTemp === NULL) {OfficeHPRequestedTemp.sendCommand(28.0)}
            elif isinstance(items["OfficeHPRequestedTemp"], UnDefType):
                events.sendCommand("OfficeHPRequestedTemp", "28.0")
            #switch(KOT)	{
            #    case KOT < HSP - 1.1						:	{	if (OfficeHPFanSpeed.state == "auto") {} else{
            #                                                        OfficeHPFanSpeed.sendCommand("auto")
            #                                            //			logInfo("KOR", "need auto OfficeHPFanSpeed is "+OfficeHPFanSpeed.state)
            #                                                        }
            #                                                        if (OfficeHPPower.state == ON) {} else { OfficeHPPower.sendCommand(ON)
            #                                            //				logInfo("KOR", "need on top OfficeHPPower is "+OfficeHPPower.state)
            #                                                            }
            #                                                        if (OfficeHPRequestedTemp.state == 28.0) {} else {
            #                                            //				logInfo("KOR", "need 28 OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
            #                                                            OfficeHPRequestedTemp.sendCommand(28)
            #                                                            }
            #                                                    }	
            elif KOT < HSP - 1.1:
                if items["OfficeHPFanSpeed"] != "auto":
                    events.sendCommand("OfficeHPFanSpeed", "auto")
                    #office_heatpump_control.log.info("need auto OfficeHPFanSpeed is {}".format(items["OfficeHPFanSpeed"]))
                if items["OfficeHPPower"] != ON:
                    events.sendCommand("OfficeHPPower", "ON")
                    #office_heatpump_control.log.info("need on top OfficeHPPower is {}".format(items["OfficeHPPower"]))
                if items["OfficeHPRequestedTemp"] != DecimalType(28.0):
                    #office_heatpump_control.log.info("need 28 OfficeHPRequestedTemp is {}".format(items["OfficeHPRequestedTemp"]))
                    events.sendCommand("OfficeHPRequestedTemp", "28.0")
            #    case KOT < HSP - 0.2 && KOT > HSP - 0.9	:	{	if (OfficeHPFanSpeed.state == "min") {} else {
            #                                                            OfficeHPFanSpeed.sendCommand("min")
            #                                                            //logInfo("KOR", "need min OfficeHPFanSpeed is "+OfficeHPFanSpeed.state)
            #                                                            }
            #                                                        if (OfficeHPRequestedTemp.state == CoolIRTemp){} else {
            #                                            //				logInfo("KOR", "need CoolIRTemp OfficeHPRequestedTemp is "+OfficeHPRequestedTemp.state)
            #                                                            OfficeHPRequestedTemp.sendCommand(CoolIRTemp)
            #                                                            }
            #                                                        if (OfficeHPPower.state == ON) {} else {
            #                                                            OfficeHPPower.sendCommand(ON)}
            #                                                            }
            elif KOT < HSP - 0.2 and KOT > HSP - 0.9:
                if items["OfficeHPFanSpeed"] != "min":
                    events.sendCommand("OfficeHPFanSpeed", "min")
                    #office_heatpump_control.log.info("need min OfficeHPFanSpeed is {}".format(items["OfficeHPFanSpeed"]))
                if items["OfficeHPPower"] != ON:
                    events.sendCommand("OfficeHPPower", "ON")
                if items["OfficeHPRequestedTemp"] != COOL_IR_TEMP:
                    #office_heatpump_control.log.info("need COOL_IR_TEMP OfficeHPRequestedTemp is {}".format(items"[OfficeHPRequestedTemp"]))
                    events.sendCommand("OfficeHPRequestedTemp", COOL_IR_TEMP.toString())
            #    case KOT > HSP								:		if (OfficeHPPower.state == OFF){} else { OfficeHPPower.sendCommand(OFF) }
            #    }
            #}
            elif KOT > HSP:
                if items["OfficeHPPower"] != OFF:
                    events.sendCommand("OfficeHPPower", "OFF")
        #} else if (OfficeHPPower.state == OFF) {} else {OfficeHPPower.sendCommand(OFF)}
        elif items["OfficeHPPower"] != OFF:
            events.sendCommand("OfficeHPPower", "OFF")
#   }
#end

Thanks @5iver that surprisingly makes sense. Does it have full vscode error checking and if it does is it the same when OH is running on the same Windows machine as vscode or does it need a seperate Linux box?
Can you recommend any “start from the beginning” documentation to read?
My plans for next week have changed and I could have a few hours spare to learn and experiment.

Scripted automation does not yet have support in the OH VSC extension, but it will get there. However, you do have pylint and a lot of other Python tools available in VSC that you do not have for the rules DSL.

Read through everything here, including the links in the References section…

https://openhab-scripters.github.io/openhab-helper-libraries/index.html

For installation, go to the beta Jython bundle topic. Installation will soon be through the UI. I plan to post a large update this weekend too. Whenever anyone asks a question of mentions something is missing, I update the docs. After pushing updates to the helper libraries, I will update the beta Jython bundle too. If you have any questions at all, just ask!

Thanks @5iver is there a dedicated new rules thread I can move this discussion to?
On a different note do have a create a jar from an pull request on github setup working? I’ve discovered the binding which was the original reason for this post has a pull request that could allow my long convoluted rule to be replaced by a later uncompiled version of the experimental Enphase Binding.

Sorry to get your topic off-track! There are several, but this is a good one…

Yes, I have an IDE setup and can build jars. There’s really not much to it, but you can do builds from the command line too.

Blockquote
Yes, I have an IDE setup and can build jars. There’s really not much to it, but you can do builds from the command line too.

Any chance you could build this one?

The other envoy request doesn’t apply to my setup

I’m sorry, but I really don’t want to be in that business. I have plenty of other things to do! Have you asked the developer for a build?

The original developer seems to have lost interest as that Pull has been sat around for over a year.
No matter, I’ll see if I can get the IDE to load without too many errors, last time I used it was a couple of years back so hopefully it will be easier!
Thanks for your help, I’ll ask any questions on a relevant thread is I experiment with JSR223-Jython