Coding advice/approach for "odd ball" amateur radio log records

In my enjoyable wanderings around NR, I've been thrilled with how easy it is to get a result. However, I'd like to go beyond that and develop better skills as I realize I made liberal use of global context that has been a pain to debug (I'm the QA team in this hobby passion).

In the amateur radio world that I enjoy so much, someone defined a "standard" contact log record called ADI which I am reasonably sure isn't bona fide XML.

I've made 100s of contacts since the New Year and would like to perform some simple analytics on who, where and when I have made contact with. Part of this is simply to train and educate my brain and I currently don't have an plans for a fancy NR dashboard or whatever.

Here's an example.

<CALL:7>M0SNA/P
   <BAND:3>10M
   <MODE:2>CW
   <QSO_DATE:8>20240131
   <TIME_ON:6>123400
   <FREQ:6>28.042
   <BAND_RX:3>10M
   <FREQ_RX:6>28.043
<EOR>

What is the best approach to converting the ADI example above to JSON such that I could more easily use/manipulate it in NR? Answers that help explain why certain suggestions are being made are very welcome.

There are a few ADIF libraries in npm. You could add an entry in the setup tab of a function node and use them for your conversions.

Example using tcadif - npm

In a function node, add tcadif to the "setup" tab, then add the below code to the "on input" tab:

const adif = tcadif.ADIF.parse(msg.payload)
msg.payload = adif.toObject()
return msg

:point_up: Untested :point_up:

Thanks Steve and should have thought about looking in NPM.

When I see semi-structured text like this, I look for patterns that can be matched with regular expressions -- for this sample record, I would skip the ADI library and just use this JSONata regex expression inside a change node:

The regex /<(\w+):(\d+)>(.+)/m matches words followed by a number inside angle brackets, iterating over the multiline input msg.record text. It returns an array of matching groups, which can be used to build an output object, with property names from the 1st capture group, and values from the 3rd capture group trimmed to the length of the 2nd capture group number.

Here is the flow I used to test it:

[{"id":"27d2dd9a875ab1a5","type":"change","z":"5ac564530cde912f","name":"parse record","rules":[{"t":"set","p":"payload","pt":"msg","to":"record.$match(/<(\\w+):(\\d+)>(.+)/m) {\t    groups[0]: groups[2].$substring(0, $number($groups[1]))\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"_mcu":{"mcu":false},"x":450,"y":280,"wires":[["b5dce41d58780fc9"]]},{"id":"2e529c446b54ac12","type":"inject","z":"5ac564530cde912f","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","_mcu":{"mcu":false},"x":130,"y":280,"wires":[["d5c901cca8d0df02"]]},{"id":"d5c901cca8d0df02","type":"template","z":"5ac564530cde912f","name":"","field":"record","fieldType":"msg","format":"handlebars","syntax":"plain","template":"<CALL:7>M0SNA/P\n   <BAND:3>10M\n   <MODE:2>CW\n   <QSO_DATE:8>20240131\n   <TIME_ON:6>123400\n   <FREQ:6>28.042\n   <BAND_RX:3>10M\n   <FREQ_RX:6>28.043\n<EOR>","output":"str","_mcu":{"mcu":false},"x":280,"y":280,"wires":[["27d2dd9a875ab1a5"]]},{"id":"b5dce41d58780fc9","type":"debug","z":"5ac564530cde912f","name":"ADI object","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":false},"x":630,"y":280,"wires":[]}]```
1 Like

Fantastic and so concise! Thanks Steve. I need to take another run at better understanding JSONATA and mastering RegEX. Nevertheless, I asked for an educational solution and got one.

Brilliant!