Problem moving javascript transform from OH1 to OH2

I’ am slowly moving my OH1 setup to OH2. I have most basic things working, and have moved on to the “next level” of issues. Most I can fix/workaround, but one I am stumped with. I have a JS transform I use to convert MQTT timestamp strings to an OpenHab DateTime item.

This is received from various owntracks devices in the format:

owntracks/homeassistant/andrew {"t":"p","tst":1461590348,"acc":0,"_type":"location","alt":0,"lon":<redacted>,"p":101.3717041015625,"lat":<redacted>,"batt":100,"tid":"AC"}
owntracks/homeassistant/wendy {"tst":1461584027,"acc":65,"_type":"location","alt":24,"lon":<redacted>,"vac":17,"p":101.0577392578125,"lat":<redacted>,"batt":95,"tid":"WC"}
owntracks/homeassistant/andrewipad {"t":"p","tst":1461537070,"acc":0,"_type":"location","alt":0,"lon":<redacted>,"p":101.3188552856445,"lat":<redacted>,"batt":84,"tid":"AC"}

where “tst” is the timestamp in unix format.

This is the old (OH1) working version:

Item:

DateTime NickEventLastUpdate "Nick's Phone Last Event Update [%1$tm/%1$td/%1$tY %1$tH:%1$tM:%1$tS]" <clock>  (gPeople) { mqtt="<[proliant:owntracks/Nick/iPhone/event:state:JS(mqttitude-timestamp.js)]" }

mqttitude-timestamp.js:

var location = eval('(' + input + ')');
var cal = new java.util.GregorianCalendar();
cal.setTimeInMillis(location.tst*1000);
var t = new org.openhab.core.library.types.DateTimeType(cal);
result = t;

This gives me an error in OH2 java.lang.ClassNotFoundException: org.openhab.core.library.types.DateTimeType, OK I thought, the old reference is probably wrong, so I replaced it with:

var location = eval('(' + input + ')');
var cal = new java.util.GregorianCalendar();
cal.setTimeInMillis(location.tst*1000);
//var t = new org.openhab.core.library.types.DateTimeType(cal);
var t = new org.eclipse.smarthome.core.library.types.DateTimeType(cal);
result = t;

Which gives me a similar error java.lang.ClassNotFoundException: org.eclipse.smarthome.core.library.types.DateTimeType.

There must be a reasonable way to convert a json unix timestamp string to a DateTime object, maybe javascript isn’t fully there yet, or I don’t understand the syntax.

Anyone know how to make this work in OH2?

Thanks,

Have you tried just returning the cal? Or just the millis? You shouldn’t have to return the actual State object from your JavaScript. It can parse and/or populate from the core primitives.

Nice thought, but I can’t get that to work. Log says:

given new state is NULL, couldn't post update for 'NickLocLastUpdateTest'

Anyone have a working example?

Have you tried to remove the package qualifier from DateTimeType? I believe the state types are imported implicitly in OH2.

I think @watou is correct. I just tested that this code:

        var cal = new java.util.GregorianCalendar();
        var t = new DateTimeType(cal)
        logInfo("test", t.toString())

works as expected. Btw, use the Eclipse SmartHome Designer 0.8.0 and it will show you the places where the parser has problems.

That was my first thought – and no, doesn’t work.

Nick Waterton P.Eng.
National Support Leader - Nuclear Medicine, PET and RP
GE Healthcare

M+1 416 859 8545
F +1 905 248 3089
E nick.waterton@med.ge.commailto:nick.waterton@med.ge.com

2300 Meadowvale Boulevard
Mississauga, ON L5N 5P9 Canada

GE imagination at work

That’s in the rules editor though (I’m currently using that as a workaround, but I need a rule for each event). I’m talking about a javascript transform – ie a .js file in the transforms directory.

Nick Waterton P.Eng.
National Support Leader - Nuclear Medicine, PET and RP
GE Healthcare

M+1 416 859 8545
F +1 905 248 3089
E nick.waterton@med.ge.commailto:nick.waterton@med.ge.com

2300 Meadowvale Boulevard
Mississauga, ON L5N 5P9 Canada

GE imagination at work

So I was making an assumption which may be wrong.

Keep in mind that JavaScript has absolutely nothing in common with Java except for the name. For some historic marketing reason they decided to call it JavaScript.

My assumption was based on the fact that it was complaining about DateTimeType that perhaps openHAB has some secret sauce that lets you get at the Java classes inside JavaScript similar to the JSR233 binding in OH 1. This is usually not the case since they are two completely different languages and it takes quite a bit of work to do something like that. And even where it is possible (e.g. the JSR233 binding), I still don’t think Java classes are allowed in JavaScript.

I’m surprised that it is not complaining about the java.util.GregorianCalendar line as well if I’m correct.

Now I’m thinking that is not the case at all.

If you just return cal and remove all references to DateTimeType do get an error on GregorianCalendar?

Yes, I get an error just returning the cal or millis.

Actually I wasn’t thinking this was real JavaScript, I was thinking that it was some OpenHAB implementation of a JavaScript like language specifically for transforms.

I mean it work fine in OH1.

Nick Waterton P.Eng

Javascript transforms work for me in OH2: example

transform file “minus3.js” contains:
(function(i){ return i -3.0; })(input)

item looks like:
Number Temperature_1249 “Temperature 12/49 [%.1f °C]” (Kitchen,All_Temperature,Node12,log) {mqtt="<[OH2MQTT:home/rfm_gw/nb/node12/dev49:state:JS(minus3.js)]"}

It is odd that it worked in OH 1 but apparently something changed and now Java classes are not accessible from the JS transform.

But you should be able to replace your code with pure JavaScript:

var location = eval('(' + input + ')');
var cal = new Date(location.tst*1000);
return cal.getTime();

Unfortunately none of this works (but thanks for looking at it).

The example given above causes an error. The correct format is:

var location = eval('(' + input + ')');
var cal = new Date(location.tst*1000);
var t = cal.getTime();
result = t;

Which also does not work (openhab reports this as NULL)… This reports the number of milliseconds (which is just tst*1000 anyway), I can get the date as a string by using:

var location = eval('(' + input + ')');
var cal = new Date(location.tst*1000);
var t = cal.toTimeString();
result = t;

But this reports the date as a String. What I’m trying to do is to get it into an Openhab DateTime object, which I can then format in Openhab. The Openhab DateTimeType will only seem to accept a returned value of an openhab DateTimeType (which is what I used to do in OH1). In OH2 I can’t use openhab types, so I can’t do it.

I’m working around this with rules at the moment, but I need a rule for each timestamp reported. If the transform worked, I wouldn’t need any rules.

So the problem remains - how do you get an openhab DateTime object from a json unix timestamp using transforms (doesn’t have to be javascript, that’s just the way I used to do it). And rules doesn’t help, as you need a rule for each timestamp conversion, which quickly becomes unworkable.

Any other suggestions?

Thanks,

According to the code there is a constructor that takes a String. Perhaps it is just a matter of finding the correct formatting of the String to return.

You should be able to combine your separate rules into one rule for all Items. For example:

rule "Convert Uptime to human readable form"
when
	Item gDeviceUptimes received update
then
	val secs = gDeviceUptimes.members.filter[GenericItem i | i instanceof NumberItem]
	
	secs.forEach[NumberItem up |
		
		if(up.state != NULL) {	
			// Get the Human Readable String Item
			val StringItem s = gDeviceUptimes.members.filter[StringItem s|s.name==up.name+"S"].head as StringItem

			// Get the associated device switch
			val swName = "N_C_" + up.name.substring(4, up.name.lastIndexOf("_"))
			val deviceStat = gDeviceStatus.members.filter[GenericItem i | i.name == swName].head as SwitchItem
			if(deviceStat != null) {

				// If OFF set to NA
				if(deviceStat.state == NULL || deviceStat.state == OFF) {
					if(s.state.toString != "NA") {
//						logInfo("network", "Updating " + s.name + " to NA")
						s.sendCommand("NA")					
					}
				}
				
				// If ON set to new value
				else {
					
					val long msec = (up.state as DecimalType).longValue
					val long sec = msec / (1000) % 60
					val long min = msec / (1000*60) % 60
					val long hour = msec / (1000*60*60) % 24
					val long day = msec / (1000*60*60*24)
				
					val StringBuilder str = new StringBuilder
					if(day > 0) {
						str.append(day)
						str.append(":")
					}
					str.append(if(hour<9) "0"+hour else hour)
					str.append(":")
					str.append(if(min<9) "0"+min else min)
					str.append(":")
					str.append(if(sec<9) "0"+sec else sec)
				
		            
					if(s.state.toString != str.toString) {
//						logInfo("network", "Updating " + s.name + " to " + str.toString)
						s.sendCommand(str.toString)	
					}
					
				}
			}
			
		}
	]
end

gDeviceUptimes contains a bunch of Number and String Items. The Number Items are populated with the number of milliseconds a device has reported being online. When one receives an update the Number is converted to Days:Hours:Minutes:Seconds.

You would have to return a string which conforms to a format accepted by DateTimeType. This can be done in plain JavaScript. If the item it’s headed towards is a DateTime item, the binding will succeed in using the string returned from the transformation to create a DateTimeType state with which to update the item.

@watou Thanks, that actually looks like it might work. I’ll give it a try.

@rlkoshak it’s hard to believe that code actually works. I have tried to get items from strings in the past, and it’s just not possible (without some ridiculous jumping through hoops), still maybe that is minimal jumping through hoops. The point really is that I shouldn’t need a rule to perform a transform, especially one that has worked fine for a long time. Converting a json unix timestamp to a DateTime object is so basic, it’s hard to believe that you can’t do it without undocumented features (ie reading the source to figure out how to do it), or complex code based on groups.

Still your code does inspire me to tackle another problem I have (ie getting items from strings). Groups is such a workaround though, To do what I want, I would have to nest groups within groups. It’s also hard to believe that Kai forgot the string item.name to item object function in openhab (although it has been requested several times).

Maybe I can resurrect that thread, Can’t use the old “not developing OH1 further so closing the issue” chestnut if we request it for OH2.

Thanks for all the suggestions.

I assure you it has been working for quite some time. And this is rule is not unusual or anything special.

One could argue that creating Java classes in the JS Transform is using an undocumented feature.

Why? An Item can be a member of any number of Groups. I’ve never had to nest Groups, at least since I was first experimenting with Groups. There really is no need and except in the limited use case of wanting to use nested Groups on the sitemap it end up being a lot. Of trouble for little benefit.

Think of a Group like a tag rather than hierarchically.

It’s possible that such a function violates some architectural principal or it hasn’t been deemed to be a high priority.

I’m not arguing that, I’m saying that it’s odd that there isn’t a way of doing it otherwise.

As to the nesting of groups within groups, it’s because when a group triggers a rule, there is no way to tell which item triggered the rule. For my application, persistence is too slow to use, so I’d have to store the current state in a hashmap, then loop around the group to see which ones changed, then loop around another group to see what the state is and update as required.

If I can get the state of the items without the group (ie via a string), I could eliminate the second loop. I can do it via a rest call, but that is sort of a workaround as well.

The feature was requested a few times, and doesn’t violate anything that I know of, but was dropped as “OH1 is not being developed any further”, this was a year or more ago. I was thinking to maybe resurrect the request as an OH2 enhancement.

Based on how all the other transforms work the expected return value is usually a String which then gets parsed into what every Item type it is destined for. In this regard the fact that the JS Transform can return other types of objects makes it the odd one.

I can’t answer why it used to work and no longer does (perhaps they chose a different embedded JavaScript implementation) but that could explain why this particular regression was never tested.

Have you considered the JSR233 binding? It sounds like it could address a whole lot of these problems, I think even the ability to get the Item that caused the Group to trigger the rule. While it is not yet compatible with OH 2, there is an issue open to which is being worked.

What you describe really isn’t nested Groups (unless there is some detail I’m not grasping). It is just two parallel groups.

And given this second loop through the Group is a one liner

MyGroup.members.filter[GenericItems i| i.name == "Name"].head

is your concern the run time cost of doing this?

I never even considered making a REST call. That isn’t a terrible idea.

If it isn’t already done, please do make the request. Just be sure to do it on the ESH github since any changes will be in ESH to support this.

@rlkoshak and @watou Thank You! I have my transform working again!

Once I realized that there was a string format that DateTime would accept (by looking at it ISO format) I was able to implement the transform as this:

var location = eval('(' + input + ')');
var tz = 5*3600000 //timezone offset in milliseconds (5 is Eastern)
var cal = new Date((location.tst*1000) - tz);
var t = cal.toISOString();
result = t;

This works!. So I can ditch the rules, and go back to the way I had it

As to the groups thing, yes I suppose it’s two parallel groups, not really groups within groups, I’m avoiding this as I don’t want to try to maintain two groups of the same items, where the difference is just in the suffix of the items names. The items are spread out over several .items files, and the chances of getting out of sync are too high. I did try the rest call, and it does work for getting the current state from a string, but it seemed a bit bonkers to do it that way. Still, if I must.

I’ll have another go at my mess of rules, having looked at your code for some inspiration.

Thanks again!.

3 Likes