MQTT Things and Channels Binding - JSONPATH at Item definition level (.items)

I am currently running 3.2.0.M3.
I was able to create a bridge and thing definition and connect to The Things Network (TTN).
Defining a JSONPATH inside the channels’ definitions was not a problem:

Thing mqtt:topic:ttn:the-thing-node "TTN The Things Node" (mqtt:broker:bae01d4af3) @ "Locker"  {
Channels:
    
    Type string : full_message  "full json message"                 [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01/up" ]
    Type datetime : time  "time of event"                       [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01/up", transformationPattern="JSONPATH:$.uplink_message.rx_metadata[0].time" ]
    Type number : rssi  "rssi value "                           [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01/up", transformationPattern="JSONPATH:$.uplink_message.rx_metadata[0].rssi" ]
    Type number : snr  "signal noise ratio"                     [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01/up", transformationPattern="JSONPATH:$.uplink_message.rx_metadata[0].snr" ]
    Type string : gateway_id  "receiving gw id"                 [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01/up", transformationPattern="JSONPATH:$.uplink_message.rx_metadata[0].gateway_ids.gateway_id" ]
    Type number : battery  "battery level device"               [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01/up", transformationPattern="JSONPATH:$.uplink_message.decoded_payload.battery" ]
    Type string : event  "event type "                          [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01/up", transformationPattern="JSONPATH:$.uplink_message.decoded_payload.event" ]
    Type number : light      "illumination in environment"      [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01//up", transformationPattern="JSONPATH:$.uplink_message.decoded_payload.light" ]
    Type number : temperature      "temperature in environment" [ stateTopic="v3/veve-lora-app@ttn/devices/veve-node-01//up", transformationPattern="JSONPATH:$.uplink_message.decoded_payloadtemperature" ]
}

My question is:
is it possible to do a JSONPATH transformation at the items (.items file) level?
I would rather have a generic channel (full_message above) from the TNN application and do the filtering at the items level.

Tried something like:

//LoraWAN Meta Data
String  ELT1_meta_time1  "Metadata Time1" { channel="mqtt:topic:ttn:the-thing-node:time" }
String  ELT1_meta_time2  "Metadata Time2" { channel="mqtt:topic:ttn:the-thing-node:full_message:JSONPATH:$.uplink_message.rx_metadata[0].time" }

Getting error in the log for ELT1_meta_time2 when saving items file:

2021-10-14 09:08:27.357 [ERROR] [el.item.internal.GenericItemProvider] - Binding configuration of type 'channel' of item 'ELT1_meta_time2' could not be parsed correctly.
org.openhab.core.model.item.BindingConfigParseException: UID segment '$.uplink_message.rx_metadata[0].time' contains invalid characters. The last segment of the channel UID must match the pattern '[\w-]*|[\w-]*#[\w-]*'.
	at org.openhab.core.model.thing.internal.GenericItemChannelLinkProvider.createItemChannelLink(GenericItemChannelLinkProvider.java:86) ~[?:?]
	at org.openhab.core.model.thing.internal.GenericItemChannelLinkProvider.processBindingConfiguration(GenericItemChannelLinkProvider.java:76) ~[?:?]
	at org.openhab.core.model.item.internal.GenericItemProvider.internalDispatchBindings(GenericItemProvider.java:372) ~[?:?]
	at org.openhab.core.model.item.internal.GenericItemProvider.internalDispatchBindings(GenericItemProvider.java:341) ~[?:?]
	at org.openhab.core.model.item.internal.GenericItemProvider.processBindingConfigsFromModel(GenericItemProvider.java:212) ~[?:?]
	at org.openhab.core.model.item.internal.GenericItemProvider.modelChanged(GenericItemProvider.java:407) ~[?:?]
	at org.openhab.core.model.core.internal.ModelRepositoryImpl.notifyListeners(ModelRepositoryImpl.java:301) ~[?:?]
	at org.openhab.core.model.core.internal.ModelRepositoryImpl.addOrRefreshModel(ModelRepositoryImpl.java:139) ~[?:?]
	at org.openhab.core.model.core.internal.folder.FolderObserver.checkFile(FolderObserver.java:249) ~[?:?]
	at org.openhab.core.model.core.internal.folder.FolderObserver.processWatchEvent(FolderObserver.java:312) ~[?:?]
	at org.openhab.core.service.WatchQueueReader.lambda$3(WatchQueueReader.java:322) ~[?:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
	at java.lang.Thread.run(Thread.java:829) [?:?]

Yes - check out Item Profiles.

And in the JSONPath docs.

Something like

To change it between item and sitemap like this if not using in rule.

String  ELT1_meta_time2 "Metadata Time2 [JSONPATH($.uplink_message.rx_metadata[0].time):%s]" {channel="mqtt:topic:ttn:the-thing-node:full_message"}

If using in a rule or something and the item needs to be a profile

The solution is:

DateTime  ELT1_meta_time2   "Metadata Time"  { channel="mqtt:topic:ttn:the-thing-node:full_message" [profile="transform:JSONPATH", function="$.uplink_message.rx_metadata[0].time"]}

Thank you @hafniumzinc and @denominator. I was not lazy, but after an hour looking through the docu I did not find it.

Out of interest, since you have the same number of Items and channel links and the system has the same processing to do, what advantage do you perceive here?

The MQTT TTN uplink messages are in the format

<AppID>/devices/<DevID>/up.

There should not be any problem to have a single Thing per AppID, but defining for each DevID an own Thing is meaningless. The idea is to have a Thing per device type and then JSPATH based on DevID on item level.

v3/veve-lora-app@ttn = AppID
veve-node-01 = DevID

But target topics to subscribe to are defined at channel level.

Unless you are using availabilityTopic features on the Thing, it’s really rather arbitrary.
You could have one Thing “owning” all channels representing a hundred different devices, each channel having its stateTopic defined separately.
Or for your own convenience, have a Thing representing each device and owning the channels of interest.

   Thing topic testing "everything" {
    Channels:
        Type string : test1  [ stateTopic="test/apple:orange/device1" ]
        Type string : test2  [ stateTopic="test/apple:orange/device2" ]
      } 

// the following functions exactly the same

   Thing topic testing1 "Device 1" {
    Channels:
        Type string : test1  [ stateTopic="test/apple:orange/device1" ]
      } 
   Thing topic testing2 "Device 2" {
    Channels:
        Type string : test2  [ stateTopic="test/apple:orange/device2" ]
      } 

There’s no performance benefits either way, just administrative ease of use.

1 Like

I think that the best solution would be to define each device type and its own application in TTN.

All devices of the same type will be registered in TTN in their own application and also share the same channels (e.g. temperature, humidity,… together with the corresponding MQTT topic names).

What needs to be done is to create a bridge-like thing that listens to all topics coming in, together with a rule which would create a new thing for each new detected device (having all channels which this type of device has).

The items would then have a simple relation to the things channels.

That’s just an ordinary topic Thing, with a string channel using wildcards in the stateTopic listened to.

True, the harder-to-do stuff is the rule for creating the specific Things and put them into the Inbox.

Yes.

Is this TTN convention self-identifying i.e. can you build a “discovery” extension for MQTT binding like e.g. ‘Homie’ etc.

Unfortunately not:

If only they would have adopted the Homie standard of MQTT it would auto discover