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
}
]