Restart only single addon

Hi,

I’m currently debugging some Binding Addon which makes it necessary to restart it regularly. The full OpenHAB instance takes about 3 minutes to restart, which is kind of tiresome if you have to do this every 30 seconds… is there a way of restarting only a certain Addon?

Thanks, Lukas

Update the timestamp on the jar file and openHAB should reload it shortly. On Linux you can do this with the “touch” command.

You can enter “ss” on the osgi console of openHab. This will give you a list of all running add-ons. Look for ID of the add-on you are willing to restart. With “stop ID” you can stop the binding and with “start ID” start it again.

Do you happen to know if it’s possible to launch the osgi console on an already-running instance of openhab? Via its PID or something?

Thanks for the answers! Just updating the timestamp works like a charm. :slight_smile: But the osgi console seems like something one should know about, thanks!

You just need to telnet to port 5555 on the openhab machine:

 telnet localhost 5555

Or if you on Linux, use the screen method to make openHAB as a service as described in the tips and tricks section. So you can all the time switch back to the OSGi console.

Thanks, you gave me enough info to get this working. It’s not quite as simple as telnet to port 5555, you also have to add 5555 after the -console line in start.sh.

i.e.

-console 5555

(or whatever port number you like).

touch does not trigger OH 2.3 to reload the files (neither jdbc.persist nor .rules).
Any idea which part of OH to restart instead of restarting the whole thing?
It takes about 1 hour in my case to start up completely (Rasp 3 with openhabian 1.4 and OH2.3).
(plus ~8000 lines of rules code)

8000 LOC? That seems like a whole lot. And that is probably the source for long startup time. Do you have a lot of duplicate code? It is probably worth working on that. Only recently have I realized how many people have such long OH startup times. I’m not sure I’d have the patience to stick with OH if I faced that. I’m willing to help if you need it.

If you follow the logs and you touch the files do you see “Refreshing model ‘file.name’” where ‘file.name’ is the jdbc.persist file or .rules file?

I just noticed after trying it out that sometimes it doesn’t reload the file after a touch. I can’t see a pattern for why it does sometimes and doesn’t other times. If you open the file and save it (maybe add a space somewhere) does the file always get picked up or is that ignored too?

But you should probably file an issue at ESH. There is no other way I know of to force OH to reload a file.

No, that’s the actual issue - I don’t see this and obviously the rules are not active accordingly.
And it does not matter, if I really changed something or just touched it.

I am sure there is a lot of improvement possible, but where to start…!? :wink:

Pick something at random, or pick something that is the most lines of code, or pick something that is the most complex, or has the most amount of repeated lines of code. Gotta start somewhere, it doesn’t really matter where.

Hmmm…
I actually added recently my Volvo into openhab.

The rules for it are already more than 800 LOC.
e.g. requesting the status of the vehicle is pretty long - but I actually don’t know how this could be simplified.

Ready for a taste? :wink:

rule "request car information"
when
	Item CarUpdate received command OFF or
	Time cron "45 0/5 * * * ?"
then
	var json = executeCommandLine("/usr/local/bin/voc -u 'xxxx' -p 'xxxx' owntracks", 10000)
// Lieber den echten timestamp extrahieren?	
//	var String time_tmp = Date.state.format("%1$td.%1$tm.%1$ty - %1$tH:%1$tM") // replaced by read out from Pulpo
	Thread::sleep(10000)
	if(json !== "Could not connect to the server.") {
		logInfo("car.rules", "json String is NOT null - starting processing...")
		//extract timestamp from vehicle
			val String time = transform("JSONPATH", "$._vehicle.ERS.timestamp", json)
			var DateTime JDT_time = parse(time.toString)    // Parse JDT object from timestring
			var DateTimeType DTT_time = new DateTimeType(JDT_time.toString)   // Convert JodaDateTime to DateTimeType

		// ****** Location *********
			val String VIN = transform("JSONPATH", "$._vehicle.VIN", json)		
			val String lat  = transform("JSONPATH", "$._vehicle.position.latitude", json)
			val String lon  = transform("JSONPATH", "$._vehicle.position.longitude", json)
//			val String head  = transform("JSONPATH", "$._vehicle.position.heading", json)
//			val String speed = transform("JS", "volvospeed.js", json)
			val String acc  = transform("JSONPATH", "$.acc", json) // gibts nur "außerhalb" von vehicle -> eigene iPhone Position?

		// ****** Service *********		
			val String fuel1 = transform("JSONPATH", "$._vehicle.fuelAmountLevel", json)
			val String fuel2 = transform("JSONPATH", "$._vehicle.fuelAmount", json)
			val String fuel3 = transform("JSONPATH", "$._vehicle.distanceToEmpty", json)
			val String fuel4 = transform("JSONPATH", "$._vehicle.averageFuelConsumption", json)
			val String odo = transform("JSONPATH", "$._vehicle.odometer", json)
			val String fluid1 = transform("JSONPATH", "$._vehicle.brakeFluid", json)
			val String fluid = fluid1.replaceAll("^\"|\"$", "")
			val String wfluid1 = transform("JSONPATH", "$._vehicle.washerFluidLevel", json)
			val String wfluid = wfluid1.replaceAll("^\"|\"$", "")
			val String bulbs = transform("JSONPATH", "$._vehicle.bulbFailures", json)
			val String engine = transform("JSONPATH", "$._vehicle.engineRunning", json) // works !!!
			val String serv1 = transform("JSONPATH", "$._vehicle.serviceWarningStatus", json)
			val String serv = serv1.replaceAll("^\"|\"$", "")

		// ****** Lock / Doors *************************	
			val String lock = transform("JSONPATH", "$._vehicle.carLocked", json)
			val String hood = transform("JSONPATH", "$._vehicle.doors.hoodOpen", json)	
			val String fld = transform("JSONPATH", "$._vehicle.doors.frontLeftDoorOpen", json)
			val String frd = transform("JSONPATH", "$._vehicle.doors.frontRightDoorOpen", json)
			val String rld = transform("JSONPATH", "$._vehicle.doors.rearLeftDoorOpen", json)
			val String rrd = transform("JSONPATH", "$._vehicle.doors.rearRightDoorOpen", json)
			val String trnk = transform("JSONPATH", "$._vehicle.doors.tailgateOpen", json)

		//********* Windows ******************************
			val String flw = transform("JSONPATH", "$._vehicle.windows.frontLeftWindowOpen", json)
			val String frw = transform("JSONPATH", "$._vehicle.windows.frontRightWindowOpen", json)
			val String rlw = transform("JSONPATH", "$._vehicle.windows.rearLeftWindowOpen", json)
			val String rrw = transform("JSONPATH", "$._vehicle.windows.rearRightWindowOpen", json)

		//********* Tyres ******************************
			val String flt1 = transform("JSONPATH", "$._vehicle.tyrePressure.frontLeftTyrePressure", json)
			val String flt = flt1.replaceAll("^\"|\"$", "")

			val String frt1 = transform("JSONPATH", "$._vehicle.tyrePressure.frontRightTyrePressure", json)
			val String frt = frt1.replaceAll("^\"|\"$", "")

			val String rlt1 = transform("JSONPATH", "$._vehicle.tyrePressure.rearLeftTyrePressure", json)
			val String rlt = rlt1.replaceAll("^\"|\"$", "")

			val String rrt1 = transform("JSONPATH", "$._vehicle.tyrePressure.rearRightTyrePressure", json)
			val String rrt = rrt1.replaceAll("^\"|\"$", "")

// Check status of heater:
			var json2 = executeCommandLine("/usr/local/bin/voc -u 'xxx' -p 'xxx' status", 10000)
			val String heater = transform("REGEX", ".*heater: (.*)", json2)
		// Heater running?	
			if(heater == "on" && CarHeater.state == OFF) {
				CarHeater.postUpdate(ON)
				sendTelegram("OH_TeleBot", "Car-Heater changed to >" + heater + "<")
			}
			if(heater == "off" && CarHeater.state == ON) {
				CarHeater.postUpdate(OFF)
				sendTelegram("OH_TeleBot", "Car-Heater changed to >" + heater + "<")
			}

		// update general information
			CarTimeSt.postUpdate(DTT_time)
			CarVIN.postUpdate(VIN)
			CarLatitude.postUpdate(lat)
			CarLongitude.postUpdate(lon)
			Location_Car.postUpdate(new PointType(CarLatitude.state + "," + CarLongitude.state))
			CarAccuracy.postUpdate(acc)
		// heading number (90) or string (E)?
			CarOdometer.postUpdate(Float::parseFloat(odo)/1000)
//			CarHeading.postUpdate(head)
//			CarSpeed.postUpdate(Float::parseFloat(speed))
				
		// update fuel level
			CarFuelPrct.postUpdate(Float::parseFloat(fuel1))
			CarFuelL.postUpdate(Float::parseFloat(fuel2))
			CarFuelLeft.postUpdate(Float::parseFloat(fuel3))
			CarAvgFuel.postUpdate(Float::parseFloat(fuel4)/10) // *0.1 ??

		// Engine running?	
			if(engine == "true") {
				CarEngine.postUpdate(ON)
			}
			if(engine == "false") {
				CarEngine.postUpdate(OFF)
			}

		// Brake Fluid?
			if(fluid == "Normal") {
				CarBrakeFluid.postUpdate(OFF)
			}
			else {
				CarBrakeFluid.postUpdate(ON)
			}

		// Wash Fluid?
			if(wfluid == "Normal") {
				CarWashFluid.postUpdate(OFF)
			}
			else {
				CarWashFluid.postUpdate(ON)
			}

		// Bulb failure?
			if(bulbs == "NULL") {// tested: == NULL / === NULL / == "NULL"
				CarBulbFail.postUpdate(OFF)
			}
			else {
				CarBulbFail.postUpdate(ON)
				sendTelegram("OH_TeleBot", "BulbFail:  >" + bulbs + "<")
			}

		// Service Warning?
			if(serv == "Normal") {
				CarServWarn.postUpdate(OFF)
			}
			else {
				CarServWarn.postUpdate(ON)
			}		

		// update locked  / door state
			if(lock == "true") {
				CarLocked.postUpdate(ON)
			}
			if(lock == "false") {
				CarLocked.postUpdate(OFF)
			}
		//*********
			if(hood == "true") {
				CarDrHood.postUpdate(ON)
			}
			if(hood == "false") {
				CarDrHood.postUpdate(OFF)
			}
		//*********
			if(fld == "true") {
				CarDrFrLeft.postUpdate(ON)
			}
			if(fld == "false") {
				CarDrFrLeft.postUpdate(OFF)
			}
		//*********
			if(frd == "true") {
				CarDrFrRght.postUpdate(ON)
			}
			if(frd == "false") {
				CarDrFrRght.postUpdate(OFF)
			}
		//*********
			if(rld == "true") {
				CarDrReLeft.postUpdate(ON)
			}
			if(rld == "false") {
				CarDrReLeft.postUpdate(OFF)
			}
		//*********
			if(rrd == "true") {
				CarDrReRght.postUpdate(ON)
			}
			if(rrd == "false") {
				CarDrReRght.postUpdate(OFF)
			}
		//*********
			if(trnk == "true") {
				CarDrTrunk.postUpdate(ON)
			}
			if(trnk == "false") {
				CarDrTrunk.postUpdate(OFF)
			}
		//*********


		// update windows state
			if(flw == "true") {
				CarWndFrLeft.postUpdate(ON)
			}
			if(flw == "false") {
				CarWndFrLeft.postUpdate(OFF)
			}
		//*********
			if(frw == "true") {
				CarWndFrRght.postUpdate(ON)
			}
			if(frw == "false") {
				CarWndFrRght.postUpdate(OFF)
			}
		//*********
			if(rlw == "true") {
				CarWndReLeft.postUpdate(ON)
			}
			if(rlw == "false") {
				CarWndReLeft.postUpdate(OFF)
			}
		//*********
			if(rrw == "true") {
				CarWndReRght.postUpdate(ON)
			}
			if(rrw == "false") {
				CarWndReRght.postUpdate(OFF)
			}
		//*********


		// update tyres state
			if(flt == "Normal") {
				CarAirFrLeft.postUpdate(OFF)
			}
			else {
				CarAirFrLeft.postUpdate(ON)
			}
		//*********
			if(frt == "Normal") {
				CarAirFrRght.postUpdate(OFF)
			}
			else {
				CarAirFrRght.postUpdate(ON)
			}
		//*********
			if(rlt == "Normal") {
				CarAirReLeft.postUpdate(OFF)
			}
			else {
				CarAirReLeft.postUpdate(ON)
			}
		//*********
			if(rrt == "Normal") {
				CarAirReRght.postUpdate(OFF)
			}
			else {
				CarAirReRght.postUpdate(ON)
			}
		//*********
			logInfo("car.rules", "The car's information was updated!")
		}
	else {
		CarResponse.postUpdate(json)
		logInfo("car.rules", "CarResponse >" + CarResponse.state + "< - Pulpo processing skipped!")
		sendTelegram("OH_TeleBot", "CarResponse:\n>" + CarResponse.state.toString + "<")
	}
	CarUpdate.sendCommand(ON)
end

Gah! That is a HUGE sleep. Why do you have to sleep here? You won’t get to this point until the executeCommandLine returns and populates json. All the sleep does here is guarantee that the Rule takes at a minimum 10 seconds and possibly up to 20 seconds to complete.

Anything longer than half a second could cause problems.

OK, here are some ideas to try:

  • Use the Exec binding configured to run periodically. Trigger the Rule when the Item linked to the output channel changes.

I wish there were a way to use a regex filter with exec like there is with MQTT. Then we can move the check for not connected to the Thing config. If you are OK with transform errors in the log from the Exec binding every time there is a not connected message you can use the regex to match against everything BUT the could not connect String.

Anyway, here is a first cut at the Thing. I’m just typing this in, there will likely be errors.

Thing exec:command:car [command="/usr/local/bin/voc -u 'xxxx' -p 'xxxx' owntracks", interval=300, timeout=10, autorun=false]

Will run every five minutes and timeout in ten seconds which should match your current cron and executeCommandLine call.

We’ll use the following Item:

String CarResponse { channel="exec:command:car:output" }
  • A few lines down, lets see what more we can do. Sadly, this sort of code is just going to be long. There isn’t much we can do about that because you need at least one line of code for every piece of data you want to extract from the message. So my next suggestion is to only extract those fields from the JSON that you are actually using. If you are not using the VIN (what would you be using it for if you only have one of these cars?) don’t bother extracting it. You can probably comment it out without causing too much extra load on the rules parser. But removing the line entirely would be even better.

  • Fail fast. You know there is nothing to do really if the response is no connection, so get that out of the way up front. This won’t save much in terms of lines of code (though sometimes it does) but it will let you drop an indent which makes the code easier to read.

rule "Received car information"
when
    Item CarResponse received update
then
    val json = CarResponse.state.toString

    if(json == "Could not connect to the server.") {
	logInfo("car.rules", "CarResponse >" + json + "< - Pulpo processing skipped!")
	sendTelegram("OH_TeleBot", "CarResponse:\n>" + json + "<")
        return;
    }

NOTE: Only use the !== and === operators when one of the arguments is literally null. (e.g. if(foo === null))

CarResponse is being populated from the Exec binding so we don’t need to trigger this Rule ourselves. Just wait for the next poll period.

Don’t try to use the state of an Item right after you postUpdate or sendCommand. It may not yet have reached that state on the next line. You already know what it is supposed to be (i.e. json) so use that in your logs instead of the state of the Item.

  • What is the format of the timestamp you get in the JSON? If it is ISO 8601 format you can just pass it in a postUpdate or sendCommand as is without needing to create a DateTimeType.

  • Don’t bother creating a variable if all you are going to do is postUpdate it later.
    CarTimeSt.postUpdate(transform("JSONPATH", "$._vehicle.ERS.timestamp", json))

  • You can string together some of these commands or use a val and save the creation of a new variable. Replace

			val String flt1 = transform("JSONPATH", "$._vehicle.tyrePressure.frontLeftTyrePressure", json)
			val String flt = flt1.replaceAll("^\"|\"$", "")

with

			var String flt = transform("JSONPATH", "$._vehicle.tyrePressure.frontLeftTyrePressure", json)
			flt = flt.replaceAll("^\"|\"$", "")

or

			val String flt1 = transform("JSONPATH", "$._vehicle.tyrePressure.frontLeftTyrePressure", json).replaceAll("^\"|\"$", "")
  • For the heater, let’s just pull it completely out of the Rule and let the Exec binding handle it.
Thing exec:command:carHeater [command="/usr/local/bin/voc -u 'xxxxxxxxx' -p 'xxxxxxxxx' status", interval=300, timeout=10, autorun=false, transform=".*heater: (.*)\U1"]

Assuming this works (I’ve never actually tried it), the \U1 will convert the first match to all uppercase.

Then change your CarHeater Item to link to the output channel on carHeater.

You can write a Rule to trigger on changes to CarHeater to send your Telegram messages.

rule "Car heater"
when
    Item CarHeater changed
then
    sendTelegram("OH_TeleBot", "Car-Heater changed to > " + CarHeater.state.toString + "<")
end
  • This probably won’t take any load off the rules parser, but you can reduce lines of code using the trinary operator.

    CarEngine.postUpdate(if(transform(“JSONPATH”, “$._vehicle.engineRunning”, json) == “true”) ON else OFF

These suggestions should give you 30-50% savings in number of lines of code.

1 Like

Wow, you‘re amazing.
I really appreciate your support and think my problems with OH sometimes might be related to my rookie programming style.

By the way:
I have recognized that I have posted my password on the original post and you copied it in yours (heater section)

I changed it already, but could you please edit your post and remove it with my email as well?

Thanks again.
I will now digest your suggestions.

I never used return.
This means, that the entire end of this rule is skipped if “could not connect to server” is true?
So nothing after this “if then” will be executed?

or do I need an else {} afterwards?

This does not work:
Configuration model 'tele.things' has errors, therefore ignoring it: [6,156]: mismatched character 'U' expecting set null

And without \U is does not work either, because the type of transform is not known:
Couldn't transform response because transformationService of type '.*heater: ' is unavailable

Is there a REGEX missing?

Oops. I didn’t even look to see what I was copying. I x’d them out in my post above.

Correct. The rule exits at that point. You do not need the else { }. In effect, everything she the if { } is the else.

Yes, look at the exec binding readme to make sure the s syntax is correct. For the \U you may need to double escape it. \U

Perfect - thanks

No worries - I messed it up in the first place :wink:

Thanks I will check it out.

What’s strange is that the CarResponse is:

>Could not connect to the server.
Could not connect to the server.<

So the string I received previously twice.

I used this:

But get an error regarding the format:
2018-07-05 07:47:15.560 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Received car information': Invalid format: "Could not connect to the server...."

Any idea what might be wrong?
I guess the return value should be a string…

Is it possible that there is a conflict if I have Things like this:

Thing exec:command:car          [command="/usr/local/bin/voc -u 'xxx' -p 'xxx' owntracks", interval=300, timeout=10, autorun=false]
Thing exec:command:carStatus    [command="/usr/local/bin/voc -u 'xxx' -p 'xxx' status", interval=300, timeout=10, autorun=false]
Thing exec:command:carTrip      [command="/usr/local/bin/voc -u 'xxx' -p 'xxx' trips", interval=300, timeout=10, autorun=false]

Do they run every 5 min at the same time?