Extracting from a string that can change

Morning all, got a little problem that's stumpted me.
I get an SMS message from an RV HVAC system that bears several metrics in a single string. I've used a change node with regex strings to extract the variables, however they can change resulting in the regex grabbing shifted parts of the message.
Any ideas on how to solve this?

This is what comes in: Note that OFF (3 characters) could be ON (2 characters) plus the values will be shorter when the temp goes below 10
Hello user, living area battery: 13.3V, 230V: ON, timer: OFF, indoor temp.: 21C, water temp.: 55C. Regards, your iNet Box
I have this in my change node:

Hi Dougle,

.. played around a bit with Javascript

[{"id":"28620f4a3b7eac27","type":"inject","z":"54efb553244c241f","name":"data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello user, living area battery: 13.3V, 230V: ON, timer: OFF, indoor temp.: 21C, water temp.: 55C. Regards, your iNet Box","payloadType":"str","x":280,"y":1000,"wires":[["e50f7462a65217b2","971950a0069ec9b0"]]},{"id":"e50f7462a65217b2","type":"function","z":"54efb553244c241f","name":"","func":"\nlet sms = msg.payload;\n\nsms = sms.slice(24)\nsms = sms.slice(0, -24)\nsms = sms.split(\", \")\n\n//node.warn(sms);\n\nlet obj = {}\n\nsms.forEach(el => {\n    obj[el.split(\":\")[0]] = el.split(\":\")[1].trim()\n})\n\nmsg.payload = obj\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":1000,"wires":[["c029a22bbb8fcd96"]]},{"id":"c029a22bbb8fcd96","type":"debug","z":"54efb553244c241f","name":"2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":640,"y":1000,"wires":[]},{"id":"971950a0069ec9b0","type":"debug","z":"54efb553244c241f","name":"1","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":400,"y":900,"wires":[]}]

image

1 Like

Hi,
That is impressive, I wish I knew how it works. It does by the way. I changed ON/OFF and reduced value to change the chr count.. Still works. Amazing. Thanks very much. Now I need to try and understand the code a little.. :smile:

EDIT: Looks like you went of the commas perhaps?

yes exactly ... once i sliced off the text at the beginning and the end i saw the pattern of commas , and then another split based on colons : to construct the object

1 Like

Very clever; now, If I can just turn the numbers in to int's rather than strings I'll be home free.. lol

hmm .. thats going to be a challenge because its not as simple as slicing off one character from the end of, lets say 13.3V to remove the V because doing that programmatically would also remove a character from ON and OFF.

so maybe we have to check what's the last character and if its a V or a C then slice it

Catch :wink:

needs some testing ...

[{"id":"28620f4a3b7eac27","type":"inject","z":"54efb553244c241f","name":"data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello user, living area battery: 13.3V, 230V: ON, timer: OFF, indoor temp.: 21C, water temp.: 55C. Regards, your iNet Box","payloadType":"str","x":280,"y":1000,"wires":[["e50f7462a65217b2","971950a0069ec9b0"]]},{"id":"e50f7462a65217b2","type":"function","z":"54efb553244c241f","name":"","func":"\nlet sms = msg.payload;\n\nsms = sms.slice(24)\nsms = sms.slice(0, -24)\nsms = sms.split(\", \")\n\n//node.warn(sms);\n\nlet obj = {}\n\nsms.forEach(el => {\n\n    let key = el.split(\":\")[0]\n    let value = el.split(\":\")[1].trim().replace(\"V\", \"\").replace(\"C\", \"\")\n\n    obj[key] = !isNaN(value) ? Number(value) : value\n})\n\nmsg.payload = obj\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":1000,"wires":[["c029a22bbb8fcd96"]]},{"id":"c029a22bbb8fcd96","type":"debug","z":"54efb553244c241f","name":"2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":640,"y":1000,"wires":[]},{"id":"971950a0069ec9b0","type":"debug","z":"54efb553244c241f","name":"1","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":400,"y":900,"wires":[]}]
1 Like

a parseInt inside a try catch should handle it (or parseFloat) - as they tend to work with strings starting with numbers.

1 Like

Thanks, however Andy's code that works really well is beyond my level of competence and thus playing with it is a bit beyond me. Perhaps I can pull the values out into change nodes to convert them?

Whenever the data is in a semi-structured string, like yours is, I reach for a regex to pull out only the data I need, and leave the rest behind:

I know regexes are not for everyone, but for your data, this expression:

/(\w[\w .]+): ([\d.]+)?(\w+)/

seems to capture the right bits (edit: added a '.' to the metric capture group):

[
    [
        "living area battery",
        "13.3",
        "V"
    ],
    [
        "230V",
        null,
        "ON"
    ],
    [
        "timer",
        null,
        "OFF"
    ],
    [
        "indoor temp.",
        "21",
        "C"
    [,
    [
        "water temp.",
        "55",
        "C"
    ]
]

I used this JSONata expression in a change node to rearrange the matched groups into an object with named metric fields:

payload.$match(/(\w[\w .]+): ([\d.]+)?(\w+)/).[groups] {
    $[0]: {
        "value": $count($) = 3 ? $number($[1]) : $[1],
        "units": $count($) = 3 ? $[2]
    }
}

Here is the json for the change node that I used, in case you want to try it out.

change.json
[
    {
        "id": "944ceeae5a30108f",
        "type": "change",
        "z": "41b4ec2d69b148dd",
        "name": "HVAC parsing",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "payload.$match(/(\\w[\\w .]+): ([\\d.]+)?(\\w+)/).[groups] {\t    $[0]: {\t        \"value\": $count($) = 3 ? $number($[1]) : $[1],\t        \"units\": $count($) = 3 ? $[2]\t    }\t}",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 350,
        "y": 660,
        "wires": [
            []
        ]
    }
]
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.