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?
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.
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.
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…!?
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?
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.
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("^\"|\"$", "")
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?
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
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: