Message originating outside meshtastic encoded with protobuf received but ignored by node

**Main question first: **
What are the minimum requirements for the RAK4631 to accept rather than ignore an incoming mqtt packet and distribute it to the other devices in the mesh?

Some background:

I am trying to send a weatherforecast from a telegram bot to meshtastic via MQTT. I am doing this as a protobuf rather than as JSON because JSON causes the RAK4631 to crash (issue #2242 on github). There are apparently a host of other bugs in JSON as well so I’ve taken the leap and tried to gain some (limited) understanding of this protobuf stuff for some time now.

The combination of RAK4621 with ethernet module plus a wireless modem with wifi switched off pulls around 1/5th of the power of a tbeam with wifi and 4G hotspot. For a remote node that’s the difference between feasible and not up here in the north.

The code I have is based on an unholy mix of reading the documentation I could find, forums, trying stuff out and then asking chatGPT for help.

I have managed to get the messages sent out to mqtt, and they also decode semi-correctly (there is some extraneous preamble that appears after decoding) but the 4G hotspot connected RAK is ignoring the message because it has “zero id”:

DEBUG | 07:27:50 7251 [RadioIf] (bw=250, sf=11, cr=4/8) packet symLen=8 ms, payloadSize=44, time 755 ms
DEBUG | 07:27:50 7251 [RadioIf] AirTime - Packet transmitted : 755ms
INFO  | 07:27:50 7251 [mqtt] Ignoring downlink message we originally sent.
DEBUG | 07:27:51 7252 [RadioIf] Completed sending (id=0x14f91c06 fr=0xca to=0xff, WantAck=0, HopLim=3 Ch=0x92 encrypted rxtime=1687937270 priority=1)
DEBUG | 07:27:59 7260 [Power] Battery: usbPower=0, isCharging=0, batMv=4052, batPct=84
DEBUG | 07:28:19 7280 [Power] Battery: usbPower=0, isCharging=0, batMv=4042, batPct=83
INFO  | 07:28:28 7289 [mqtt] Received MQTT topic msh/2/c/V??r/, len=178
DEBUG | 07:28:28 7289 [Router] Ignoring message with zero id
DEBUG | 07:28:28 7289 [Router] Ignoring message with zero id
DEBUG | 07:28:28 7289 [Router] handleReceived(REMOTE) (id=0x00000000 fr=0x00 to=0x00, WantAck=0, HopLim=0 Ch=0x0 Portnum=0 rxtime=1687937308)
DEBUG | 07:28:28 7289 [Router] Module 'routing' wantsPacket=1
INFO  | 07:28:28 7289 [Router] Received routing from=0x0, id=0x0, portnum=0, payloadlen=169
DEBUG | 07:28:28 7289 [Router] Routing sniffing (id=0x00000000 fr=0x00 to=0x00, WantAck=0, HopLim=0 Ch=0x0 Portnum=0 rxtime=1687937308)
DEBUG | 07:28:28 7289 [Router] Module 'routing' considered
DEBUG | 07:28:39 7300 [Power] Battery: usbPower=0, isCharging=0, batMv=4031, batPct=81

There are a lot of id’s floating around in the documentation which I have tried to give a non-zero value to, including “id” “channel_id” “gateway_id” and possibly others. I am doing this in node-red so partly based on the position encode example given here: MQTT | Meshtastic, but have changed the encode node to “MeshPacket” rather than “Position” for lack of another reasonable option, the output of this is then again encoded with mqtt.proto as a “ServiceEnvelope” and passing through those nodes and visible in mqtt.

What are the minimum requirements for the RAK4631 to accept rather than ignore an incoming mqtt packet and distribute it to the other devices in the mesh?

This is the node-red flow:

The original message output from the telegram bot is:

{"chatId":-616175758,"type":"message","content":{"message_id":4523,"from":{"id":6543218166,"is_bot":true,"first_name”:”Weather”,”username”:”Forecast”},”chat":{"id":-616175758,"title”:”Weatherforecast”,”type":"group","all_members_are_administrators":true},"date":1687945747,"text":"2023-06-28T12:00:00Z:  Vær 1h: partlycloudy_day; Temp 1h: 21.1°C ; Nedbør 1h: 0mm ; Vindretning 1h: ØSØ ; Vindstyrke: 2.7m/s , i kastene 6.6m/s ; Tåke: 0% Torden: 0.3%"},"options":{"chat_id":-616175758,"text":"2023-06-28T12:00:00Z:  Vær 1h: partlycloudy_day; Temp 1h: 21.1°C ; Nedbør 1h: 0mm ; Vindretning 1h: ØSØ ; Vindstyrke: 2.7m/s , i kastene 6.6m/s ; Tåke: 0% Torden: 0.3%"},"sentMessageId":4523}

This passes through a function node. Here I am obviously lacking some of the parameters since the message originates outside meshtastic. I’ve tried to place in som reasonable placeholders just to see if I can pass the message, but expect there are some issues here:

// Get the 'content.text' field which is a string
var contentText = msg.payload.content.text;
var id = msg.payload.sentMessageId;
var channelId = msg.payload.content.date;
var gatewayId = msg.payload.options.chat_id;
var portnum = 1;
// Convert the string into a Buffer with UTF-8 encoding,
// then encode that Buffer into a Base64 string
var base64EncodedContentText = Buffer.from(contentText, 'utf8').toString('base64');

// Clear protobufType property,, this gets set by encode node to MeshPacket in next step
msg.protobufType = null;

// Update the payload
msg.payload = {
    "packet": {
        "from": gatewayId, //flow.get("from_outbound"),
        "to": flow.get("to_outbound"), //undefined, this is a leftover from the example
        "decoded": {
            "portnum": portnum, //flow.get("portnum_outbound"),
            "payload": base64EncodedContentText
        }
    },
    "id": id,
    "channelId": channelId,//flow.get("channelId_outbound"),
    "gatewayId": gatewayId, //flow.get("gatewayId_outbound"),
};
return msg;

The output from the function node is then

{"packet":{"from":-616175758,"decoded":{"portnum":1,"payload":"MjAyMy0wNi0yOFQxMjowMDowMFo6ICBWw6ZyIDFoOiBwYXJ0bHljbG91ZHlfZGF5OyBUZW1wIDFoOiAyMS4xwrBDIDsgTmVkYsO4ciAxaDogMG1tIDsgVmluZHJldG5pbmcgMWg6IMOYU8OYIDsgVmluZHN0eXJrZTogMi43bS9zICwgaSBrYXN0ZW5lIDYuNm0vcyA7IFTDpWtlOiAwJSBUb3JkZW46IDAuMyU="}},"id":4523,"channelId":1687945747,"gatewayId":-616175758}

This is passed through the mqtt.proto node and passes fine. Decoding with the mqtt decode node leads to

{"packet":{"from":3658801542,"decoded":{"portnum":"TEXT_MESSAGE_APP","payload":"MjAyMy0wNi0yOFQxMjowMDowMFo6ICBWw6ZyIDFoOiBwYXJ0bHljbG91ZHlfZGF5OyBUZW1wIDFoOiAyMS4xwrBDIDsgTmVkYsO4ciAxaDogMG1tIDsgVmluZHJldG5pbmcgMWg6IMOYU8OYIDsgVmluZHN0eXJrZTogMi43bS9zICwgaSBrYXN0ZW5lIDYuNm0vcyA7IFTDpWtlOiAwJSBUb3JkZW46IDAuMyU="}}}

I used the decode function at the bottom rhs to convert this from base64 to UTF8:

� �� �"�    � 2023-06-28T12:00:00Z:  Vær 1h: partlycloudy_day; Temp 1h: 21.1°C ; Nedbør 1h: 0mm ; Vindretning 1h: ØSØ ; Vindstyrke: 2.7m/s , i kastene 6.6m

There are some extraneous characters at the beginning of the sequence, not sure where they come from but otherwise OK.

How can i fix this issue of the 4G router node ignoring the incoming message and not passing it on to the network?

INFO  | 07:28:28 7289 [mqtt] Received MQTT topic msh/2/c/V??r/, len=178
DEBUG | 07:28:28 7289 [Router] Ignoring message with zero id
DEBUG | 07:28:28 7289 [Router] Ignoring message with zero id
DEBUG | 07:28:28 7289 [Router] handleReceived(REMOTE) (id=0x00000000 fr=0x00 to=0x00, WantAck=0, HopLim=0 Ch=0x0 Portnum=0 rxtime=1687937308)
DEBUG | 07:28:28 7289 [Router] Module 'routing' wantsPacket=1
INFO  | 07:28:28 7289 [Router] Received routing from=0x0, id=0x0, portnum=0, payloadlen=169
DEBUG | 07:28:28 7289 [Router] Routing sniffing (id=0x00000000 fr=0x00 to=0x00, WantAck=0, HopLim=0 Ch=0x0 Portnum=0 rxtime=1687937308)
DEBUG | 07:28:28 7289 [Router] Module 'routing' considered

Any pointers or help greatly appreciated!

Same for all hardware, identical Lora config, a client has to make the UInt32 packet id.

Thanks for trying to help @garth. I’m not sure you read my whole post, but I don’t know how any client, independent of hardware used, makes the Uint32 packet id, and am trying to find out. I was elaborating on my choice of hardware to save people from suggesting to use t-beam, wifi and JSON.

Following your suggestion that the “id” concerned in this case is in fact called “packet id” and meant to be formatted as UInt32, I have searched the following documentation for further, actionable inspiration:

LoRa Configuration | Meshtastic which has no relevant information;
Buf Schema Registry under message Config.LoRaConfig, which has no “packet id” parameter or other “id” parameter of any kind,
under message MeshPacket, which I assumed would be the right way to go in my initial post, where there is an “id” as fixed32, not uint32 though (might be interchangable?), and no “packet id”, anywhere in that documentation

Some elaboration would therefore be very helpful - in the function node to pass to the encoder, do i call it “id”, packet_id or something else? Is there a document that describes this Lora config in a more pedagogical way than the Buf Schema Registry? or ideally a code snippet you could point to from the firmware or some other external app that tries to encode a message that hasn’t come from a hardware device with the prerequisite firmware? Is it in fact the MeshPacket or some packet wrapped inside another one such that one would first encode using the mesh.proto and then the mqtt.proto?

For these details you’d probably need to look in the source code, for example the Python CLI.
You have to create a packet ID yourself; the CLI just picks a random uint32, see: python/meshtastic/mesh_interface.py at master · meshtastic/python · GitHub
As you can see, the field is called ‘id’ within the MeshPacket.

Somehow the packet that ends up in the firmware also doesn’t have the ‘from’ and ‘to’ fields set as you can see from your logs.

Thanks @garth and @GUVWAF for those suggestions, I finally figured it out with your help.

Messages now show up on the devices serial when running -noproto, and they also show up on the screens of the other nodes (at least on some of them, some of the time, maybe I’ve exceeded the airtime).

They do not show up in the iOS app or on the Android app of the phones connected to the devices though - maybe I need to reinstall and re-pair, although messages sent from phone to phone via meshtastic do work, so I suspect there is further work to be done. I am assuming that setting “to” to 0 means it’ll be sent to all?

For now the function looks like this:

// Generate a packetId
var packetId = Math.floor(Math.random() * Math.pow(2, 32));
// Get the current timestamp in milliseconds
var currentTimeMillis = new Date().getTime();

// Convert to seconds and round down to the nearest whole number
var currentTimeSeconds = Math.floor(currentTimeMillis / 1000);

// Get the 'content.text' field which is a string
var contentText = msg.payload.content.text;
var channelId = "Vær";
var gatewayId = "!716012b";
var portnum = 1;
// Convert the string into a Buffer with UTF-8 encoding,
// then encode that Buffer into a Base64 string
var base64EncodedContentText = Buffer.from(contentText, 'utf8').toString('base64');

var sender = 172350952;
var receiver = 4294967295;
var snr = 5;
var rssi = 0;


// Create a JSON representation of your Protobuf message
msg.payload =
{
    "packet": {
        "from": sender,
        "to": receiver,
        "channel": 3,
        "decoded": {
            "portnum": portnum, 
            "payload": base64EncodedContentText,
        },
        "id": packetId,
        "rx_time": currentTimeSeconds,
        "rx_snr" : snr,
        "hop_limit": 3, // Set as required
        "want_ack": false, // Set as required
        "rx_rssi" : rssi,
    },
    "channel_id": channelId,
    "gateway_id": gatewayId,
};

return msg;

edit> code above was edited with @AndreK suggestion and is now working perfectly

1 Like

make sure you’re setting the ‘to’ field, use 0xffffffff / 4294967295 for broadcast.

2 Likes

Fantastic, now the messages get forwarded to all devices, including on the phones correctly, thank you so much! Should really have understood this myself since the “from” field is formatted the same way, this was super helpful.

Finally, its taken me a while to get this far, all is working!

Hi @Nanovitruvius,
could you post an export of your node red flow? I’m trying something similar with weather warnings from the German Weather Service (Deutscher Wetterdienst).
But somewhere in my flow a mistake must have crept in…

Hi @elrir: Here is my flow - it basically allows a command such as /weather 2 3 to be picked up by a telegram bot or from the mesh and then 3 forcasts with a 2 hour interval are sent to the mesh. Its possible to replace this with a injection node to trigger e.g. a 24 h forecast with e.g. 3 hours intervals every day at some preset hour as well. good luck!

[
    {
        "id": "4c01979ae92e62d2",
        "type": "comment",
        "z": "7e3a38c28989362d",
        "name": "Complete api.met.no content with altitude",
        "info": "https://api.met.no/weatherapi/locationforecast/2.0/complete?lat={{{lat}}}&lon={{{lon}}}&altitude=210",
        "x": 620,
        "y": 480,
        "wires": []
    },
    {
        "id": "94ae1c0511ec05b5",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "Select relevant metrics",
        "func": "let timeDate = msg.payload.time;\n\n\nlet weatherType1h = msg.payload.data.next_1_hours.summary.symbol_code;\nlet temperature = msg.payload.data.instant.details.air_temperature;\nlet precipitationAmount1h = msg.payload.data.next_1_hours.details.precipitation_amount;\nlet windDirection = msg.payload.data.instant.details.wind_from_direction;\nlet windSpeed = msg.payload.data.instant.details.wind_speed;\nlet gustSpeed = msg.payload.data.instant.details.wind_speed_of_gust;\nlet fogFraction = msg.payload.data.instant.details.fog_area_fraction;\nlet thunderProbability = msg.payload.data.next_1_hours.details.probability_of_thunder;\nlet weatherType6h = msg.payload.data.next_6_hours.summary.symbol_code;\nlet precipitationAmount6h = msg.payload.data.next_6_hours.details.precipitation_amount;\nlet precipitationProbability6h = msg.payload.data.next_6_hours.details.probability_of_precipitation;\n//let temperature = ; \nlet windDirectionInCardinal;\n\nswitch (true) {\n    case windDirection >= 11.25 && windDirection < 33.75:\n        windDirectionInCardinal = 'NNØ';\n        break;\n    case windDirection >= 33.75 && windDirection < 56.25:\n        windDirectionInCardinal = 'NØ';\n        break;\n    case windDirection >= 56.25 && windDirection < 78.75:\n        windDirectionInCardinal = 'ØNØ';\n        break;\n    case windDirection >= 78.75 && windDirection < 101.25:\n        windDirectionInCardinal = 'Ø';\n        break;\n    case windDirection >= 101.25 && windDirection < 123.75:\n        windDirectionInCardinal = 'ØSØ';\n        break;\n    case windDirection >= 123.75 && windDirection < 146.25:\n        windDirectionInCardinal = 'SØ';\n        break;\n    case windDirection >= 146.25 && windDirection < 168.75:\n        windDirectionInCardinal = 'SSØ';\n        break;\n    case windDirection >= 168.75 && windDirection < 191.25:\n        windDirectionInCardinal = 'S';\n        break;\n    case windDirection >= 191.25 && windDirection < 213.75:\n        windDirectionInCardinal = 'SSV';\n        break;\n    case windDirection >= 213.75 && windDirection < 236.25:\n        windDirectionInCardinal = 'SV';\n        break;\n    case windDirection >= 236.25 && windDirection < 258.75:\n        windDirectionInCardinal = 'VSV';\n        break;\n    case windDirection >= 258.75 && windDirection < 281.25:\n        windDirectionInCardinal = 'V';\n        break;\n    case windDirection >= 281.25 && windDirection < 303.75:\n        windDirectionInCardinal = 'VNV';\n        break;\n    case windDirection >= 303.75 && windDirection < 326.25:\n        windDirectionInCardinal = 'NV';\n        break;\n    case windDirection >= 326.25 && windDirection < 348.75:\n        windDirectionInCardinal = 'NNV';\n        break;\n    default:\n        windDirectionInCardinal = 'N';\n        break;\n}\n\n// Concatenate the values of \"sender\" and \"textToSend\" into a single string\nmsg.payload = timeDate + \": \"  + \n\" Vær 1h: \" + weatherType1h + \";\" +\n\" Temp 1h: \" + temperature + \"°C ;\" +\n\" Nedbør 1h: \" + precipitationAmount1h + \"mm ;\" +\n\" Vindretning 1h: \" + windDirectionInCardinal + \" ;\" +\n\" Vindstyrke: \" + windSpeed + \"m/s ,\" +\n\" i kastene \" + gustSpeed + \"m/s ;\" +\n\" Tåke: \" + fogFraction + \"%\" +\n\" Torden: \" + thunderProbability + \"%\";\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1900,
        "y": 480,
        "wires": [
            [
                "bf3047ee493920a3"
            ]
        ]
    },
    {
        "id": "bf3047ee493920a3",
        "type": "change",
        "z": "7e3a38c28989362d",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "text",
                "pt": "flow",
                "to": "payload",
                "tot": "msg",
                "dc": true
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 2150,
        "y": 480,
        "wires": [
            [
                "53497606cf5bae79"
            ]
        ]
    },
    {
        "id": "94c33f0f8569a5a0",
        "type": "telegram sender",
        "z": "7e3a38c28989362d",
        "name": "Weatherbot",
        "bot": "",
        "haserroroutput": true,
        "outputs": 2,
        "x": 2210,
        "y": 620,
        "wires": [
            [
                "2dbdcdf3876a08fc"
            ],
            []
        ]
    },
    {
        "id": "6d7fa613a901877d",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "Send to chat",
        "func": "msg.payload = { chatId: -12345678, type: 'message', content: msg.payload }\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1850,
        "y": 640,
        "wires": [
            [
                "94c33f0f8569a5a0"
            ]
        ]
    },
    {
        "id": "53497606cf5bae79",
        "type": "simple-queue",
        "z": "7e3a38c28989362d",
        "name": "",
        "firstMessageBypass": false,
        "bypassInterval": "30000",
        "x": 2360,
        "y": 480,
        "wires": [
            [
                "6d7fa613a901877d"
            ]
        ]
    },
    {
        "id": "3ab871c199e62232",
        "type": "mqtt out",
        "z": "7e3a38c28989362d",
        "name": "MQTT to weather",
        "topic": "msh/2/c/weather/",
        "qos": "2",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "",
        "x": 3170,
        "y": 640,
        "wires": []
    },
    {
        "id": "a02f75288b8e95d0",
        "type": "telegram command",
        "z": "7e3a38c28989362d",
        "name": "Set Forecast",
        "command": "/vær",
        "description": "",
        "registercommand": false,
        "language": "",
        "scope": "default",
        "bot": "",
        "strict": false,
        "hasresponse": true,
        "useregex": false,
        "removeregexcommand": false,
        "outputs": 2,
        "x": 190,
        "y": 320,
        "wires": [
            [
                "2ac7159619cdb350",
                "6c75e231cb3a9f9c"
            ],
            []
        ]
    },
    {
        "id": "2ac7159619cdb350",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "Parse interval and number of Forecasts",
        "func": "// Extract the content from the message\nvar content = msg.payload.content;\n\n// Check if the content property exists and is a string\nif (content && typeof content === \"string\") {\n    // Trim the content string to remove leading and trailing whitespace\n    content = content.trim();\n\n    // Split the content string by space to extract x and y\n    var x = content.split(\" \")[0];\n    var y = content.split(\" \")[1];\n\n    // Set the values of x and y as properties of the msg.payload object\n    msg.payload = {\n        x: x,\n        y: y\n    };\n} else {\n    // If the content property does not exist or is not a string, send an error message\n    node.error(\"Invalid message payload\");\n}\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 800,
        "y": 260,
        "wires": [
            [
                "f96278dbb08c53b7"
            ]
        ]
    },
    {
        "id": "c4e92e70e4d3ca58",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "Selected Weatherforecasts - iterate every x entries for y entries",
        "func": "var counter = 0;\n\n// Extract x and y from the msg object\n// Assume x and y are provided as properties of the msg.payload object\nvar x = msg.payload[0].x;\nvar y = msg.payload[0].y;\n\nmsg.payload[1].properties.timeseries.forEach(function (entry, index) {\n    if ((index + 1) % x === 0) {\n        // Set the current entry as the msg.payload\n        msg.payload = entry;\n\n        // Increment the counter\n        counter++;\n\n        // If the counter is less than or equal to y, send the message\n        if (counter <= y) {\n            node.send(msg);\n        } else {\n            // Otherwise, exit the loop\n            return;\n        }\n    }\n});\n",
        "outputs": 2,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1410,
        "y": 260,
        "wires": [
            [
                "94ae1c0511ec05b5"
            ],
            []
        ]
    },
    {
        "id": "f96278dbb08c53b7",
        "type": "join",
        "z": "7e3a38c28989362d",
        "name": "",
        "mode": "custom",
        "build": "array",
        "property": "payload",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "accumulate": false,
        "timeout": "",
        "count": "2",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "",
        "reduceFixup": "",
        "x": 1090,
        "y": 260,
        "wires": [
            [
                "c4e92e70e4d3ca58"
            ]
        ]
    },
    {
        "id": "5d6eb9b85ef0cf71",
        "type": "http request",
        "z": "7e3a38c28989362d",
        "name": "Complete api.met.no",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://api.met.no/weatherapi/locationforecast/2.0/complete?lat={{{lat}}}&lon={{{lon}}}&altitude=210",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 620,
        "y": 440,
        "wires": [
            [
                "1d5971b9cb95ac4d"
            ]
        ]
    },
    {
        "id": "6c75e231cb3a9f9c",
        "type": "change",
        "z": "7e3a38c28989362d",
        "name": "set geolocation",
        "rules": [
            {
                "t": "set",
                "p": "lat",
                "pt": "msg",
                "to": "53.627748",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "lon",
                "pt": "msg",
                "to": "8.423197",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "headers",
                "pt": "msg",
                "to": "[{\"User-Agent\":\"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36\"}]",
                "tot": "json"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 460,
        "y": 340,
        "wires": [
            [
                "93e32afe73f15412",
                "d593e5fb3bd739c8"
            ]
        ]
    },
    {
        "id": "1d5971b9cb95ac4d",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "save met_public_forecast",
        "func": "if (msg.payload) {\n    flow.set(\"met_public_forecast\", msg.payload);\n    return msg;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 870,
        "y": 440,
        "wires": [
            [
                "f96278dbb08c53b7",
                "51529c29b2d8d3f0"
            ]
        ]
    },
    {
        "id": "93e32afe73f15412",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "check met_public_forecast",
        "func": "var moment = context.global.get(\"moment\");\nvar now = moment();\n\nvar met_public_forecast = flow.get(\"met_public_forecast\") || null;\nif (met_public_forecast && met_public_forecast.properties) {\n    var now = moment();\n    var updated = moment(met_public_forecast.properties.meta.updated_at);\n    var difference = now.diff(updated, \"hours\");\n    if (difference < 12) {\n        // cached data\n        msg.payload = met_public_forecast;\n        node.send([null, msg]);\n        return\n    }\n}\n\nnode.warn(\"update weather from met\");\nnode.send([msg, null]);",
        "outputs": 2,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 700,
        "y": 360,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "d593e5fb3bd739c8",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "headers",
        "func": "msg.headers = {};\nmsg.headers['USER-AGENT'] = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36';\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 400,
        "wires": [
            [
                "5d6eb9b85ef0cf71"
            ]
        ]
    },
    {
        "id": "51529c29b2d8d3f0",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "Generate Meshtastic String",
        "func": "let timeDate = msg.payload.properties.timeseries[0].time;\nlet sender = \"weather forecast for\";\nlet type = \"sendtext\";\n\nlet weatherType1h = msg.payload.properties.timeseries[0].data.next_1_hours.summary.symbol_code;\nlet temperature = msg.payload.properties.timeseries[0].data.instant.details.air_temperature; \nlet precipitationAmount1h = msg.payload.properties.timeseries[0].data.next_1_hours.details.precipitation_amount;\nlet windDirection = msg.payload.properties.timeseries[0].data.instant.details.wind_from_direction; \nlet windSpeed = msg.payload.properties.timeseries[0].data.instant.details.wind_speed; \nlet gustSpeed = msg.payload.properties.timeseries[0].data.instant.details.wind_speed_of_gust; \nlet fogFraction = msg.payload.properties.timeseries[0].data.instant.details.fog_area_fraction; \nlet thunderProbability = msg.payload.properties.timeseries[0].data.next_1_hours.details.probability_of_thunder; \nlet weatherType6h = msg.payload.properties.timeseries[0].data.next_6_hours.summary.symbol_code; \nlet precipitationAmount6h = msg.payload.properties.timeseries[0].data.next_6_hours.details.precipitation_amount; \nlet precipitationProbability6h = msg.payload.properties.timeseries[0].data.next_6_hours.details.probability_of_precipitation; \n//let temperature = ; \n\n// Concatenate the values of \"sender\" and \"textToSend\" into a single string\nlet weatherString = sender + timeDate + \": Vær 1h: \" + weatherType1h + \"; Temp 1h: \" + temperature + \"*C; Nedbør 1h: \" + precipitationAmount1h + \"mm\";\n\n// Assign the combined string to the \"payload\" property of the \"msg\" object\nmsg.payload = {\n    sender: sender,\n    type: type,\n    payload: weatherString\n};\n\nreturn msg;",
        "outputs": 2,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1180,
        "y": 400,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "c36d268d4ed94aa5",
        "type": "mqtt in",
        "z": "7e3a38c28989362d",
        "name": "",
        "topic": "msh/2/c/weather/#",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 150,
        "y": 640,
        "wires": [
            [
                "fec6b846b82f5640"
            ]
        ]
    },
    {
        "id": "fec6b846b82f5640",
        "type": "decode",
        "z": "7e3a38c28989362d",
        "name": "decode Protobuf MQTT",
        "protofile": "16dcac1d72a8f14a",
        "protoType": "ServiceEnvelope",
        "x": 370,
        "y": 640,
        "wires": [
            [
                "55e8b19aca872844"
            ]
        ]
    },
    {
        "id": "55e8b19aca872844",
        "type": "switch",
        "z": "7e3a38c28989362d",
        "name": "switch manual decoding nested message based on portum",
        "property": "payload.packet.decoded.portnum",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "TEXT_MESSAGE_APP",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "POSITION_APP",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "NODEINFO_APP",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 3,
        "x": 660,
        "y": 560,
        "wires": [
            [
                "65475abb9a1c6893"
            ],
            [],
            []
        ]
    },
    {
        "id": "65475abb9a1c6893",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "function get the message as string from TEXT_MESSAGE_APP",
        "func": "msg.payload = msg.payload.packet.decoded.payload;\n\nlet bufferObj = Buffer.from(msg.payload, \"base64\");\nlet decodedString = bufferObj.toString(\"utf8\");\nmsg.payload = decodedString;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 910,
        "y": 620,
        "wires": [
            [
                "1d2fc1d44457f33e"
            ]
        ]
    },
    {
        "id": "1d2fc1d44457f33e",
        "type": "change",
        "z": "7e3a38c28989362d",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "text",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 610,
        "y": 660,
        "wires": [
            [
                "62758b12e214b009"
            ]
        ]
    },
    {
        "id": "62758b12e214b009",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "Filter for /vær and parse x for hours and y for number of forecasts",
        "func": "msg.payload = {\n    content: msg.payload\n};\n\n// Extract the content from the message\nvar content = msg.payload.content;\n\n// Check if the content property exists and is a string\nif (content && typeof content === \"string\") {\n    // Trim the content string to remove leading and trailing whitespace\n    content = content.trim();\n\n    // Check if the content string starts with \"/vær\"\n    if (content.startsWith(\"/vær\")) {\n        // Split the content string by space to extract x and y\n        var x = content.split(\" \")[1];\n        var y = content.split(\" \")[2];\n\n        // Check if the split values are numbers\n        if (!isNaN(x) && !isNaN(y)) {\n            // Set the values of x and y as properties of the msg.payload object\n            msg.payload = {\n                x: x,\n                y: y\n            };\n            node.send(msg);\n        } else {\n            node.error(\"Invalid message payload, x and y should be numbers\");\n        }\n    } else {\n        node.error(\"Ignored - not a request for forecast, should start with '/vær'\");\n    }\n} else {\n    // If the content property does not exist or is not a string, send an error message\n    node.error(\"Invalid message payload\");\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1000,
        "y": 680,
        "wires": [
            [
                "2ac7159619cdb350",
                "6c75e231cb3a9f9c"
            ]
        ]
    },
    {
        "id": "cae338a1d4f835df",
        "type": "comment",
        "z": "7e3a38c28989362d",
        "name": "Receives weather request from weather Channel on Meshtastic",
        "info": "# Command must be formatted as /vær x y, where x is the number of hours between forecasts and y is the number of forecasts to send",
        "x": 230,
        "y": 600,
        "wires": []
    },
    {
        "id": "651d6f8b247ac047",
        "type": "encode",
        "z": "7e3a38c28989362d",
        "name": "Encode Protobuf MQTT",
        "protofile": "16dcac1d72a8f14a",
        "protoType": "ServiceEnvelope",
        "x": 2910,
        "y": 640,
        "wires": [
            [
                "3ab871c199e62232",
                "d0f42ec1d9892a9b",
                "c88bbbbf372d324c"
            ]
        ]
    },
    {
        "id": "d0f42ec1d9892a9b",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": "decode",
        "func": "// Assume that 'msg.payload' contains the Base64 encoded string\nvar base64EncodedStr = msg.payload;\n\n// Decode the Base64 string back to UTF-8\nvar decodedStr = Buffer.from(base64EncodedStr, 'base64').toString('utf8');\n\n// Output the decoded string\nmsg.payload = decodedStr;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 3140,
        "y": 700,
        "wires": [
            [
                "636ff1d5b2c65d5a"
            ]
        ]
    },
    {
        "id": "636ff1d5b2c65d5a",
        "type": "debug",
        "z": "7e3a38c28989362d",
        "name": "decoded protobuf",
        "active": false,
        "tosidebar": true,
        "console": true,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 3350,
        "y": 700,
        "wires": []
    },
    {
        "id": "c88bbbbf372d324c",
        "type": "decode",
        "z": "7e3a38c28989362d",
        "name": "test decode Protobuf",
        "protofile": "16dcac1d72a8f14a",
        "protoType": "ServiceEnvelope",
        "x": 3180,
        "y": 500,
        "wires": [
            [
                "a23556d24b9617b5"
            ]
        ]
    },
    {
        "id": "a23556d24b9617b5",
        "type": "debug",
        "z": "7e3a38c28989362d",
        "name": "test entire payload",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 3389,
        "y": 499,
        "wires": []
    },
    {
        "id": "2dbdcdf3876a08fc",
        "type": "function",
        "z": "7e3a38c28989362d",
        "name": " assemble forecast",
        "func": "// Generate a packetId\nvar packetId = Math.floor(Math.random() * Math.pow(2, 32));\n// Get the current timestamp in milliseconds\nvar currentTimeMillis = new Date().getTime();\n\n// Convert to seconds and round down to the nearest whole number\nvar currentTimeSeconds = Math.floor(currentTimeMillis / 1000);\n\n// Get the 'content.text' field which is a string\nvar contentText = msg.payload.content.text;\nvar channelId = \"Weather\";\nvar gatewayId = \"!f8210fca\";\nvar portnum = 1;\n// Convert the string into a Buffer with UTF-8 encoding,\n// then encode that Buffer into a Base64 string\nvar base64EncodedContentText = Buffer.from(contentText, 'utf8').toString('base64');\n\nvar sender = 4145418186; // was 4145418186 but this clashes with being on same network as 4G router\nvar receiver = 4294967295; // dec equivalent of 0xffffffff for broadcast\nvar snr = 5;\nvar rssi = -13;\n\n\n// Create a JSON representation of your Protobuf message\nmsg.payload =\n{\n    \"packet\": {\n        \"from\": sender,\n        \"to\": receiver,\n        \"channel\": 3,\n        \"decoded\": {\n            \"portnum\": portnum, \n            \"payload\": base64EncodedContentText,\n        },\n        \"id\": packetId,\n        \"rx_time\": currentTimeSeconds,\n        \"rx_snr\" : snr,\n        \"hop_limit\": 3, // Set as required\n        \"want_ack\": false, // Set as required\n        \"rx_rssi\" : rssi,\n    },\n    \"channel_id\": channelId,\n    \"gateway_id\": gatewayId,\n};\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 2510,
        "y": 640,
        "wires": [
            [
                "651d6f8b247ac047"
            ]
        ]
    },
    {
        "id": "db73eb973dcd2623",
        "type": "comment",
        "z": "7e3a38c28989362d",
        "name": "make sure \"keep_snake_case\" is ticked if you update protobufs!!!! ",
        "info": "otherwise itl crash the 4g routers",
        "x": 500,
        "y": 700,
        "wires": []
    },
    {
        "id": "da5e0824245df56c",
        "type": "comment",
        "z": "7e3a38c28989362d",
        "name": "Two network nodes active will result in 2x messages triggered",
        "info": "",
        "x": 2500,
        "y": 440,
        "wires": []
    },
    {
        "id": "16dcac1d72a8f14a",
        "type": "protobuf-file",
        "protopath": "/home/nano/protobufs/protobufs/mqtt.proto",
        "watchFile": true,
        "keepCase": true
    }
]