Trying to extract GPS coordinates from JSON

Hi,

I have an incoming MQTT topic which looks like:

{
  "timeBootMs": 600681604,
  "lat": 530218818,
  "lon": 57019666,
  "alt": -91020,
  "relativeAlt": -86482,
  "vx": -2,
  "vy": 5,
  "vz": 2,
  "hdg": 25473
}

I wish to extract the lat, lon and alt like to it formats like a Location value. I have found that I can use JSONPATH to extract multiple values.

JSONPATH:$.['lat', 'lon', 'alt']

This gives me:

{lat=530217673, lon=57020082, alt=-90260}

Next I want to chain a RegEx to only leave the values and comma’s:

REGEX:lat=(-?\d+), lon=(-?\d+), alt=(-?\d+)∩JSONPATH:$.['lat', 'lon', 'alt']

And then the channel is no longer updating. I do not get errors in the logs. RegEx transformation is installed. When I try the RegEx expression online, it does parse the string correcty. I have tested it with a String item to test the results but just no error and no item update.

Any idea what i’m doing wrong here?

Regards,
Bastiaan

Why not using three separate channels, for each value one?

Than you can use e.g.

JSONPATH:$.lat

and you have the correct value

Reverse the order: jsonpath first, then regex

Unfortunately REGEX in OH works a little differently from standard REGEX which makes certain operations one might do with REGEX not work in OH. The overall rules are:

  1. the expression must match the entire String/document, not just the line you want
  2. the first matching group (i.e. first set of parens) is what gets returned by the transform

So even after you take the suggestion from @JimT, this might not work given the restrictions above.

Overall, this is probably going to be easiest using a SCRIPT transform. Since the “JS” in JSON stands for JavaScript using JS makes a lot of sense.

The following should work:

(function(i){
  const parsed = JSON.parse(i);
  return parsed.lat + ', ' + parsed.lon + ', ' + parsed.alt;
})(input)

Depending on how you define this (UI or in a file) for the MQTT config it would be one of the following:

JS:config:js:getCoord

JS:getCoord.js

The first would be if it’s defined in the UI with a UID of “getCoord” and the second is if it’s placed in a file named “getCoord.js” under $OH_CONF/transformations.

It might work as an inline script but I’m not sure. I’ve never tried a two line inline script.

JS:| var parsed = JSON.parse(input); parsed.lat + ', ' + parsed.lon + ', ' + parsed.alt;

or if that doesn’t work

JS:| var parsed = JSON.parse(input); return parsed.lat + ', ' + parsed.lon + ', ' + parsed.alt;

It would be super inefficient but the following will definitely work:

JS:| JSON.parse(input).lat + ', ' + JSON.parse(input).lon + ', ' + JSON.parse(input).alt;

but parsing the input three times feels icky.

You can use REGEX transform to achieve what you want. I just didn’t test your expression, but now I have. This will work:

JSONPATH:$.['lat', 'lon', 'alt']∩REGEX:s/.*lat=(-?\d+), lon=(-?\d+), alt=(-?\d+).*/$1,$2,$3/

Note, the above works if you created the Thing in UI and pasted that in the channel config. But if you’re creating your things using .things file, double the backslash like this:

JSONPATH:$.['lat', 'lon', 'alt']∩REGEX:s/.*lat=(-?\\d+), lon=(-?\\d+), alt=(-?\\d+).*/$1,$2,$3/

Example of a .things file:

Thing mqtt:topic:mosquitto:test3 (mqtt:broker:mosquitto) {
  Channels:
    Type string: test [stateTopic="test/test", transformationPattern="JSONPATH:$.['lat', 'lon', 'alt']∩REGEX:s/.*lat=(-?\\d+), lon=(-?\\d+), alt=(-?\\d+).*/$1,$2,$3/"]
}

Actually, you can even just use REGEX and skip the JSONPATH completely, if you’re sure the json string will be consistent. You just have to account for the newlines (add \s* or something)

Thing mqtt:topic:mosquitto:test3 (mqtt:broker:mosquitto) {
  Channels:
    Type string: test [stateTopic="test/test", transformationPattern="REGEX:s/.*\"lat\": (-?\\d+), \"lon\": (-?\\d+), \"alt\": (-?\\d+).*/$1,$2,$3/"]
}

Alternatively you can also do this using JS Scripting as above, or in JRuby

RB:|require 'json'; JSON.parse(input).slice('lat', 'lon', 'alt').values.join(',')

Explanation:

require "json"     # in Ruby you need to do this to use JSON.parse
JSON.parse(input)  # converts the json string into a Hash
    .slice("lat", "lon", "alt") # returns a hash with only those keys { lat=xxxx, lon=xxxx, alt=xxxx }
    .values        # returns only the values as an array: [xxx,xxxx,xxxx]
    .join(",")     # Returns a string by joining the elements of the array with a comma: "xxxx,xxxx,xxx"

Using JS / JRuby to parse the JSON instead of using regex is more robust and able to handle cases where the json field order is not as expected, or some fields are missing. But It is probably not likely to happen in your situation.

2 Likes

Thank you both @JimT and @rlkoshak for your very in depth explanation. By joining the groups in the REGEX expression the result indeed is as expected.

Good to know the REGEX is somewhat different from I used to.

I pursue a clean as possible OH setup, so I stick with the REGEX/JSON path solution, and not with additional JS files.

Again, thank you very much!

Since OH 4 you can define the transform through the UI. You don’t need any files.

I also showed above how you could use an inline JS script which doesn’t require any file nor does it require defining it through the UI.

I’m not saying you should change because what you’ve done is perfectly acceptable. But the Script transform can be just as “clean”.

Interesting, are you referring to Profile and Profile configuration part?

Settings → Transformations

1 Like

Hi I have a similar problem I wanted to use the same solution but it did not work.

My Tasmota send out this

tele/WEMOS_GPS/SENSOR = {"Time":"2023-11-20T21:30:08","GPS":{"lat":47.2590716,"lon":16.6037582,"alt":274.929,"hAcc":16.315,"vAcc":17.206,"fix":"3D-fix"},"FLOG":{"rec":0,"mode":0,"sec":0}}

My channel is defined az location:

  - id: Longitude
    channelTypeUID: mqtt:number
    label: Longitude
    description: null
    configuration:
      stateTopic: tele/WEMOS_GPS/SENSOR
      transformationPattern: JSONPATH:$.GPS.lon
  - id: GPS_loc
    channelTypeUID: mqtt:location
    label: GPSloc
    description: ""
    configuration:
      stateTopic: tele/WEMOS_GPS/SENSOR
      transformationPattern: JSONPATH:$.GPS.['lat', 'lon',
        'alt']∩REGEX:s/.*lat=(-?\d+), lon=(-?\d+), alt=(-?\d+).*/$1,$2,$3/

In case of only longitude or latitude it work well but as location I receive null.

Any Idea

regards Zoltan

Your values have decimal points which isn’t accounted for in your regex.

Try

s/.*lat=(-?[0-9.]+), lon=(-?[0-9.]+), alt=(-?[0-9.]+).*/$1,$2,$3/

Thanks a lot it helped